added email verification

This commit is contained in:
andrei-mihnea-cerbu
2025-02-04 19:08:34 +02:00
parent 9baec7a7cb
commit dcf505c89b
67 changed files with 3922 additions and 3638 deletions
+1
View File
@@ -14,6 +14,7 @@ export let operationCodes = {
LOGIN: 'LOGIN', LOGIN: 'LOGIN',
SIGN_UP: 'SIGN_UP', SIGN_UP: 'SIGN_UP',
RESET_PASSWORD: 'RESET_PASSWORD', RESET_PASSWORD: 'RESET_PASSWORD',
EMAIL_VERIFICATION: 'EMAIL_VERIFICATION',
FIND_BY_EMAIL: 'FIND_BY_EMAIL', FIND_BY_EMAIL: 'FIND_BY_EMAIL',
MODIFY_USER: 'MODIFY_USER', MODIFY_USER: 'MODIFY_USER',
@@ -9,6 +9,7 @@ export class AuthOperations implements OperationPlugin {
LOGIN: 'LOGIN', LOGIN: 'LOGIN',
SIGN_UP: 'SIGN_UP', SIGN_UP: 'SIGN_UP',
RESET_PASSWORD: 'RESET_PASSWORD', RESET_PASSWORD: 'RESET_PASSWORD',
EMAIL_VERIFICATION: 'EMAIL_VERIFICATION',
}; };
// Utility function to validate email format // Utility function to validate email format
@@ -147,10 +148,35 @@ export class AuthOperations implements OperationPlugin {
} }
} }
public static async handleEmailVerification(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
const { email } = parsedMessage.metaInfo || {};
if (!AuthOperations.isValidEmail(email)) {
return {
operationCode: operationCodes.ERR,
metaInfo: { message: 'Invalid email format.' },
};
}
const user = userDatabase.findByEmail(email);
if (!user) {
return {
operationCode: operationCodes.ERR,
metaInfo: { message: 'User not found.' },
};
}
return {
operationCode: operationCodes.OK,
metaInfo: { message: 'Email verified successfully.' },
};
}
// Register operations with the OperationHandler // Register operations with the OperationHandler
public register(operationHandler: OperationHandler): void { public register(operationHandler: OperationHandler): void {
operationHandler.registerHandler(AuthOperations.operationCodes.LOGIN, AuthOperations.handleLogin); operationHandler.registerHandler(AuthOperations.operationCodes.LOGIN, AuthOperations.handleLogin);
operationHandler.registerHandler(AuthOperations.operationCodes.SIGN_UP, AuthOperations.handleSignUp); operationHandler.registerHandler(AuthOperations.operationCodes.SIGN_UP, AuthOperations.handleSignUp);
operationHandler.registerHandler(AuthOperations.operationCodes.RESET_PASSWORD, AuthOperations.handleResetPassword); operationHandler.registerHandler(AuthOperations.operationCodes.RESET_PASSWORD, AuthOperations.handleResetPassword);
operationHandler.registerHandler(AuthOperations.operationCodes.EMAIL_VERIFICATION, AuthOperations.handleEmailVerification);
} }
} }
+3
View File
@@ -0,0 +1,3 @@
node_modules
dist
package-lock.json
+7
View File
@@ -0,0 +1,7 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"tabWidth": 2,
"printWidth": 100
}
+36 -9
View File
@@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"check-disk-space": "^3.4.0", "check-disk-space": "^3.4.0",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"jsonfile": "^6.1.0",
"ping": "^0.4.4", "ping": "^0.4.4",
"uuid": "^10.0.0" "uuid": "^10.0.0"
}, },
@@ -20,12 +21,14 @@
"@electron-forge/maker-rpm": "^6.0.0", "@electron-forge/maker-rpm": "^6.0.0",
"@electron-forge/maker-squirrel": "^6.0.0", "@electron-forge/maker-squirrel": "^6.0.0",
"@electron-forge/maker-zip": "^6.0.0", "@electron-forge/maker-zip": "^6.0.0",
"@types/jsonfile": "^6.1.4",
"@types/ping": "^0.4.4", "@types/ping": "^0.4.4",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"del-cli": "^5.0.0", "del-cli": "^5.0.0",
"electron": "^33.0.2", "electron": "^33.0.2",
"node-disk-info": "^1.3.0", "node-disk-info": "^1.3.0",
"prettier": "^3.4.2",
"typescript": "^5.6.2" "typescript": "^5.6.2"
} }
}, },
@@ -864,6 +867,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/jsonfile": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz",
"integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/keyv": { "node_modules/@types/keyv": {
"version": "3.1.4", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
@@ -2685,9 +2698,9 @@
} }
}, },
"node_modules/execa/node_modules/cross-spawn": { "node_modules/execa/node_modules/cross-spawn": {
"version": "6.0.5", "version": "6.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz",
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3297,7 +3310,7 @@
"version": "4.2.11", "version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true, "devOptional": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/hard-rejection": { "node_modules/hard-rejection": {
@@ -3846,7 +3859,6 @@
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"universalify": "^2.0.0" "universalify": "^2.0.0"
@@ -5056,6 +5068,22 @@
"node": "^12.20.0 || >=14" "node": "^12.20.0 || >=14"
} }
}, },
"node_modules/prettier": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/proc-log": { "node_modules/proc-log": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz",
@@ -6279,7 +6307,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 10.0.0" "node": ">= 10.0.0"
@@ -6505,9 +6532,9 @@
} }
}, },
"node_modules/yarn-or-npm/node_modules/cross-spawn": { "node_modules/yarn-or-npm/node_modules/cross-spawn": {
"version": "6.0.5", "version": "6.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz",
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
+4
View File
@@ -4,6 +4,7 @@
"version": "1.0.0", "version": "1.0.0",
"description": "Aplicatie P2P pentru stocarea resurselor digitale", "description": "Aplicatie P2P pentru stocarea resurselor digitale",
"scripts": { "scripts": {
"format": "prettier --write ./src/**/*.ts",
"clean": "del-cli dist && del-cli out", "clean": "del-cli dist && del-cli out",
"build-dist": "tsc && copyfiles -u 1 'src/**/*' dist", "build-dist": "tsc && copyfiles -u 1 'src/**/*' dist",
"start-dev": "tsc && copyfiles -u 1 'src/**/*' dist && electron dist/main/main.js -- --expose-gc", "start-dev": "tsc && copyfiles -u 1 'src/**/*' dist && electron dist/main/main.js -- --expose-gc",
@@ -17,6 +18,7 @@
"dependencies": { "dependencies": {
"check-disk-space": "^3.4.0", "check-disk-space": "^3.4.0",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"jsonfile": "^6.1.0",
"ping": "^0.4.4", "ping": "^0.4.4",
"uuid": "^10.0.0" "uuid": "^10.0.0"
}, },
@@ -26,12 +28,14 @@
"@electron-forge/maker-rpm": "^6.0.0", "@electron-forge/maker-rpm": "^6.0.0",
"@electron-forge/maker-squirrel": "^6.0.0", "@electron-forge/maker-squirrel": "^6.0.0",
"@electron-forge/maker-zip": "^6.0.0", "@electron-forge/maker-zip": "^6.0.0",
"@types/jsonfile": "^6.1.4",
"@types/ping": "^0.4.4", "@types/ping": "^0.4.4",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"copyfiles": "^2.4.1", "copyfiles": "^2.4.1",
"del-cli": "^5.0.0", "del-cli": "^5.0.0",
"electron": "^33.0.2", "electron": "^33.0.2",
"node-disk-info": "^1.3.0", "node-disk-info": "^1.3.0",
"prettier": "^3.4.2",
"typescript": "^5.6.2" "typescript": "^5.6.2"
}, },
"config": { "config": {
+1 -10
View File
@@ -1,11 +1,9 @@
// Load announcement content from the application info when the page loads // Load announcement content from the application info when the page loads
async function loadAnnouncement() { async function loadAnnouncement() {
try { try {
const announcementText = await window.electronAPI.readApplicationInfo('announcement'); const announcementText = await window.electronAPI.readAnnouncement();
const announcementContent = document.getElementById('announcement-content'); const announcementContent = document.getElementById('announcement-content');
if (announcementContent && announcementText) { if (announcementContent && announcementText) {
console.log('Announcement:', announcementText);
announcementContent.innerHTML = formatTextForHtml(announcementText); announcementContent.innerHTML = formatTextForHtml(announcementText);
} }
} catch (error) { } catch (error) {
@@ -15,14 +13,8 @@ async function loadAnnouncement() {
} }
function formatTextForHtml(text) { function formatTextForHtml(text) {
// Replace newlines with <br> tags
let formattedText = text.replace(/\n/g, '<br>'); let formattedText = text.replace(/\n/g, '<br>');
// Replace tabs with a few non-breaking spaces for indentation
formattedText = formattedText.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;'); formattedText = formattedText.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;');
// Replace other special characters as needed
// Example: Handle double spaces by converting to &nbsp;
formattedText = formattedText.replace(/ /g, ' &nbsp;'); formattedText = formattedText.replace(/ /g, ' &nbsp;');
return formattedText; return formattedText;
@@ -30,6 +22,5 @@ function formatTextForHtml(text) {
// Close the window when the close button is clicked // Close the window when the close button is clicked
function closeWindow() { function closeWindow() {
window.electronAPI.writeApplicationInfo('announcement', '');
window.electronAPI.closeAnnouncementWindow(); window.electronAPI.closeAnnouncementWindow();
} }
+3 -3
View File
@@ -13,7 +13,7 @@ function fadeOut(destination) {
container.addEventListener('animationend', async () => { container.addEventListener('animationend', async () => {
try { try {
await window.electronAPI.changeContent(destination); await window.uiAPI.changeContent(destination);
console.log('Navigated to', destination); console.log('Navigated to', destination);
} catch (error) { } catch (error) {
console.error('Error navigating:', error); console.error('Error navigating:', error);
@@ -24,9 +24,9 @@ function fadeOut(destination) {
async function waitForResponse() { async function waitForResponse() {
return new Promise((resolve) => { return new Promise((resolve) => {
const idResponseCheck = setInterval(async () => { const idResponseCheck = setInterval(async () => {
if (await window.electronAPI.hasResponseArrived()) { if (await window.networkAPI.hasResponseArrived()) {
clearInterval(idResponseCheck); clearInterval(idResponseCheck);
resolve(await window.electronAPI.getLastUcResult()); // Resolve the response or null if not available resolve(await window.networkAPI.getLastUcResult()); // Resolve the response or null if not available
} }
}, 100); // Check every 100 milliseconds if the response has arrived }, 100); // Check every 100 milliseconds if the response has arrived
}); });
+33 -42
View File
@@ -8,10 +8,12 @@ document.addEventListener('DOMContentLoaded', async function () {
const resetPasswordButton = document.getElementById('resetPassword'); const resetPasswordButton = document.getElementById('resetPassword');
const signUpButton = document.getElementById('signup'); const signUpButton = document.getElementById('signup');
await window.databaseAPI.setLoginStatus(false);
// Retrieve the operation codes via IPC // Retrieve the operation codes via IPC
const operationCodes = await window.electronAPI.getOperationsCodes(); const operationCodes = await window.networkAPI.getOperationsCodes();
if (!operationCodes) { if (!operationCodes) {
await window.electronAPI.showAlert('Internal error of the application. Failed to retrieve operation codes.'); await window.uiAPI.showAlert('Internal error of the application. Failed to retrieve operation codes.');
return; return;
} }
@@ -22,12 +24,12 @@ document.addEventListener('DOMContentLoaded', async function () {
resetPasswordButton.addEventListener('click', function (e) { resetPasswordButton.addEventListener('click', function (e) {
e.preventDefault(); e.preventDefault();
window.electronAPI.changeContent('reset_password'); window.uiAPI.changeContent('reset_password');
}); });
signUpButton.addEventListener('click', function (e) { signUpButton.addEventListener('click', function (e) {
e.preventDefault(); e.preventDefault();
window.electronAPI.changeContent('sign_up'); window.uiAPI.changeContent('sign_up');
}); });
// Submit button logic (handle login) // Submit button logic (handle login)
@@ -42,50 +44,52 @@ document.addEventListener('DOMContentLoaded', async function () {
const password = formData.get('password'); const password = formData.get('password');
// Open a TCP socket to the stored IP // Open a TCP socket to the stored IP
if (!await window.electronAPI.openUcSocket()) { if (!await window.networkAPI.openUcSocket()) {
await window.electronAPI.showAlert('Internal error of the application. Unable to open socket.'); await window.uiAPI.showAlert('Internal error of the application. Unable to open socket.');
return; return;
} }
// Attempt login // Attempt login
if (!await attemptLogin(email, password)) { if (!await attemptLogin(email, password)) {
await window.electronAPI.closeUcSocket(); await window.networkAPI.closeUcSocket();
return; return;
} }
// Fetch and store user info // Fetch and store user info
if (!await fetchAndStoreUserInfo(email)) { if (!await fetchAndStoreUserInfo(email)) {
await window.electronAPI.closeUcSocket(); await window.networkAPI.closeUcSocket();
return; return;
} }
// Fetch user info from local storage // Fetch user info from local storage
const userInfo = await window.electronAPI.readUserConfig('user_info'); const userInfo = await window.databaseAPI.getUserInfo('user_info');
if (!userInfo) { if (!userInfo) {
await window.electronAPI.closeUcSocket(); await window.networkAPI.closeUcSocket();
await window.electronAPI.showAlert('Failed to fetch user info.'); await window.uiAPI.showAlert('Failed to fetch user info.');
return; return;
} }
// Fetch and store encryption key // Fetch and store encryption key
if (!await fetchAndStoreEncryptionKey(userInfo.id)) { if (!await fetchAndStoreEncryptionKey(userInfo.id)) {
await window.electronAPI.closeUcSocket(); await window.networkAPI.closeUcSocket();
return; return;
} }
// Close the socket and navigate to main menu after success // Close the socket and navigate to main menu after success
await window.electronAPI.closeUcSocket(); await window.networkAPI.closeUcSocket();
await window.electronAPI.changeContent('main_menu'); await window.uiAPI.startWorkers();
await window.databaseAPI.setLoginStatus(true);
await window.uiAPI.changeContent('main_menu');
}); });
}); });
async function attemptLogin(email, password) { async function attemptLogin(email, password) {
const app_type = await window.electronAPI.readUserConfig('app_type'); const app_type = await window.databaseAPI.getAppType();
const messageData = {email, password, app_type}; const messageData = {email, password, app_type};
// Send the login message to the server // Send the login message to the server
if (!await window.electronAPI.sendUcMessage(codeLogin, messageData)) { if (!await window.networkAPI.sendUcMessage(codeLogin, messageData)) {
await window.electronAPI.showAlert('Failed to send login request.'); await window.uiAPI.showAlert('Failed to send login request.');
return false; return false;
} }
@@ -93,57 +97,44 @@ async function attemptLogin(email, password) {
const response = await waitForResponse(); const response = await waitForResponse();
if (!response) { if (!response) {
await window.electronAPI.showAlert('No response from server.'); await window.uiAPI.showAlert('No response from server.');
return false; return false;
} }
if (response.operationCode !== codeOk) { if (response.operationCode !== codeOk) {
await window.electronAPI.showAlert(response.metaInfo.message); await window.uiAPI.showAlert(response.metaInfo.message);
return false; return false;
} }
const user_info = await window.electronAPI.readUserConfig('user_info');
if (!user_info || (user_info && user_info.email !== email)) {
await window.electronAPI.resetApplicationInfo();
await window.electronAPI.resetMemory();
await window.electronAPI.resetUserConfig();
await window.electronAPI.writeUserConfig('app_type', app_type);
await window.electronAPI.writeUserConfig('user_info', {email, password});
return true;
}
return true; return true;
} }
async function fetchAndStoreUserInfo(userEmail) { async function fetchAndStoreUserInfo(userEmail) {
if (!await window.electronAPI.sendUcMessage(codeFindByEmail, {email: userEmail})) { if (!await window.networkAPI.sendUcMessage(codeFindByEmail, {email: userEmail})) {
await window.electronAPI.showAlert('Failed to send request to fetch user info.'); await window.uiAPI.showAlert('Failed to send request to fetch user info.');
return false; return false;
} }
// Wait for the response using waitForResponse // Wait for the response using waitForResponse
const response = await waitForResponse(); const response = await waitForResponse();
if (response && response.operationCode === codeOk) { if (response && response.operationCode === codeOk) {
let userInfo = await window.electronAPI.readUserConfig('user_info'); const userInfo = {};
if (!userInfo) {
userInfo = {};
}
userInfo.id = response.metaInfo.id; userInfo.id = response.metaInfo.id;
userInfo.email = response.metaInfo.email;
userInfo.departmentId = response.metaInfo.departmentId; userInfo.departmentId = response.metaInfo.departmentId;
userInfo.name = response.metaInfo.name || 'User'; userInfo.name = response.metaInfo.name || 'User';
await window.electronAPI.writeUserConfig('user_info', userInfo); await window.databaseAPI.writeUserInfo(userInfo);
return true; return true;
} }
await window.electronAPI.showAlert('Failed to fetch user info from server.'); await window.uiAPI.showAlert('Failed to fetch user info from server.');
return false; return false;
} }
async function fetchAndStoreEncryptionKey(userId) { async function fetchAndStoreEncryptionKey(userId) {
if (!await window.electronAPI.sendUcMessage(codeFindKeyByUser, {userId})) { if (!await window.networkAPI.sendUcMessage(codeFindKeyByUser, {userId})) {
await window.electronAPI.showAlert('Failed to send request to fetch encryption key.'); await window.uiAPI.showAlert('Failed to send request to fetch encryption key.');
return false; return false;
} }
@@ -155,10 +146,10 @@ async function fetchAndStoreEncryptionKey(userId) {
iv: response.metaInfo.key.iv, iv: response.metaInfo.key.iv,
}; };
await window.electronAPI.writeUserConfig('encryption_key', encryptionKey); await window.databaseAPI.writeEncryptionKey(encryptionKey);
return true; return true;
} }
await window.electronAPI.showAlert('Failed to fetch encryption key from server.'); await window.uiAPI.showAlert('Failed to fetch encryption key from server.');
return false; return false;
} }
+33 -83
View File
@@ -1,3 +1,7 @@
let backupDirId = '';
let shareDirId = '';
let departmentDirId = '';
document.addEventListener('DOMContentLoaded', async function () { document.addEventListener('DOMContentLoaded', async function () {
setInterval(loadReceivedFiles, 3000); // Call loadReceivedFiles every 3000 ms (3 seconds) setInterval(loadReceivedFiles, 3000); // Call loadReceivedFiles every 3000 ms (3 seconds)
@@ -47,16 +51,15 @@ async function initialSetup(){
} }
async function restoreBackup() { async function restoreBackup() {
const backupDirectory = await window.electronAPI.readApplicationInfo('backupDirectory'); const backupDirectory = await window.databaseAPI.isBackupSet();
if (backupDirectory && backupDirectory.path) { if (backupDirectory) {
// If backup directory is already set, display an alert await window.uiAPI.showAlert('You have already set a backup directory. You cannot restore again.');
await window.electronAPI.showAlert('You have already set a backup directory. You cannot restore again.');
return; return;
} }
// Prompt the user to choose the destination for the restored backup // Prompt the user to choose the destination for the restored backup
const destinationPath = await window.electronAPI.selectDirectory(); const destinationPath = await window.uiAPI.selectDirectory();
if (!destinationPath) { if (!destinationPath) {
return; // User canceled the directory selection return; // User canceled the directory selection
} }
@@ -69,26 +72,31 @@ async function restoreBackup() {
} }
async function checkAndSetAllDirectories() { async function checkAndSetAllDirectories() {
await attachNotificationButton('backupDirectory', 'Set your backup directory!', 'backup_alert', 'alert'); const directorySchemes = await window.databaseAPI.getLocalResources();
await attachNotificationButton('shareDirectory', 'Set your share directory!', 'share_alert', 'alert');
await attachNotificationButton('departmentDirectory', 'Set your department directory!', 'department_alert', 'alert'); backupDirId = directorySchemes.backup.id;
shareDirId = directorySchemes.shared.id;
departmentDirId = directorySchemes.department.id;
await attachNotificationButton(backupDirId, 'Set your backup directory!', 'backup_alert', 'alert');
await attachNotificationButton(shareDirId, 'Set your share directory!', 'share_alert', 'alert');
await attachNotificationButton(departmentDirId, 'Set your department directory!', 'department_alert', 'alert');
} }
async function checkPathExistence(pathKey) { async function checkPathExistence(id) {
return await window.electronAPI.readApplicationInfo(pathKey); const dirInfo = await window.databaseAPI.getDirectoryInfo(id);
return dirInfo.path !== ''
} }
async function setPath(pathKey) { async function setPath(id){
const path = await window.electronAPI.selectDirectory(); const path = await window.uiAPI.selectDirectory();
if (path === undefined) return; if (path === undefined) return false;
const id = await window.electronAPI.createMemoryEntry() return await window.databaseAPI.writeDirectoryPath(id, path);
console.log({id, path})
await window.electronAPI.writeApplicationInfo(pathKey, {id, path});
} }
async function attachNotificationButton(pathKey, buttonText, buttonId, buttonName) { async function attachNotificationButton(entryId, buttonText, buttonId, buttonName) {
const path = await checkPathExistence(pathKey); const path = await checkPathExistence(entryId);
if (!path) { if (!path) {
const notificationsDiv = document.getElementById('notifications'); const notificationsDiv = document.getElementById('notifications');
const button = document.createElement('button'); const button = document.createElement('button');
@@ -97,8 +105,7 @@ async function attachNotificationButton(pathKey, buttonText, buttonId, buttonNam
button.name = buttonName; button.name = buttonName;
button.textContent = buttonText; button.textContent = buttonText;
button.addEventListener('click', async function () { button.addEventListener('click', async function () {
await setPath(pathKey); if(await setPath(entryId)) button.remove();
button.remove(); // Remove button after setting the path
}); });
notificationsDiv.appendChild(button); notificationsDiv.appendChild(button);
} }
@@ -108,7 +115,7 @@ async function fetchUserInfo() {
const usernameField = document.getElementById('username-field'); const usernameField = document.getElementById('username-field');
// Read the user credentials from the userConfig // Read the user credentials from the userConfig
let userInfo = await window.electronAPI.readUserConfig('user_info'); let userInfo = await window.databaseAPI.getUserInfo();
if (userInfo && userInfo.name) { if (userInfo && userInfo.name) {
usernameField.textContent = userInfo.name; usernameField.textContent = userInfo.name;
return; return;
@@ -116,7 +123,7 @@ async function fetchUserInfo() {
// Update the greeting with the fetched user's name // Update the greeting with the fetched user's name
if (usernameField) { if (usernameField) {
usernameField.textContent = userInfo.name; // Update the h1 with the user's name usernameField.textContent = userInfo.name;
} else { } else {
console.error("Username field is not available in the DOM."); console.error("Username field is not available in the DOM.");
} }
@@ -124,29 +131,14 @@ async function fetchUserInfo() {
async function loadReceivedFiles() { async function loadReceivedFiles() {
// Read the shareDirectory from applicationInfo // Read the shareDirectory from applicationInfo
const shareDirectoryData = await window.electronAPI.readApplicationInfo('shareDirectory'); const shareDirData = await window.databaseAPI.getDirectoryInfo(shareDirId);
if (!shareDirectoryData || !shareDirectoryData.id) {
console.log('No received files or directory ID found.');
return;
}
const directoryId = shareDirectoryData.id;
// Fetch the directory structure (JSON objects with user names and file paths)
const directoryStructure = await window.electronAPI.readMemoryEntry(directoryId);
if (!directoryStructure || !directoryStructure.structure) {
console.log('No received files found in the directory structure.');
return;
}
const notificationsDiv = document.getElementById('notifications'); const notificationsDiv = document.getElementById('notifications');
const existingButtons = Array.from(notificationsDiv.children).map(button => button.getAttribute('data-filepath')); const existingButtons = Array.from(notificationsDiv.children).map(button => button.getAttribute('data-filepath'));
// Iterate over each user and their files in the structure // Iterate over each user and their files in the structure
Object.keys(directoryStructure.structure).forEach(userName => { Object.keys(shareDirData.structure).forEach(userName => {
const userFiles = directoryStructure.structure[userName]; const userFiles = shareDirData.structure[userName];
// Iterate over each file of the user // Iterate over each file of the user
Object.keys(userFiles).forEach(fileName => { Object.keys(userFiles).forEach(fileName => {
@@ -166,56 +158,14 @@ async function loadReceivedFiles() {
}); });
} }
async function removeReceivedFile(filePath) {
// Read the shareDirectory from applicationInfo
const shareDirectoryData = await window.electronAPI.readApplicationInfo('shareDirectory');
if (!shareDirectoryData || !shareDirectoryData.id) {
console.log('No directory ID found to update.');
return;
}
const directoryId = shareDirectoryData.id;
// Fetch the directory structure
let directoryStructure = await window.electronAPI.readMemoryEntry(directoryId);
if (directoryStructure && directoryStructure.structure) {
// Iterate over each user and their files
for (let userName in directoryStructure.structure) {
let userFiles = directoryStructure.structure[userName];
// Check if the file exists and remove it
if (userFiles[filePath]) {
delete userFiles[filePath];
// Remove the user if no more files are left
if (Object.keys(userFiles).length === 0) {
delete directoryStructure.structure[userName];
}
// Update the directory structure in memory
await window.electronAPI.updateMemoryEntry(directoryId, directoryStructure);
console.log(`File ${filePath} removed from memory.`);
return;
}
}
} else {
console.log('No directory structure found in memory to update.');
}
}
async function handleFileReceivedButtonPressed(filePath, button) { async function handleFileReceivedButtonPressed(filePath, button) {
console.log('Notification button clicked!'); console.log('Notification button clicked!');
// Open the file in the file explorer // Open the file in the file explorer
await window.electronAPI.showFileInExplorer(filePath) await window.uiAPI.showFileInExplorer(filePath)
.then(() => console.log('File explorer opened for: ' + filePath)) .then(() => console.log('File explorer opened for: ' + filePath))
.catch(error => console.error('Error opening file explorer:', error)); .catch(error => console.error('Error opening file explorer:', error));
// Remove the button after opening the file // Remove the button after opening the file
button.remove(); button.remove();
// Remove the file from memory
await removeReceivedFile(filePath);
} }
+17 -14
View File
@@ -1,22 +1,27 @@
let id = '';
let departmentId = '';
document.addEventListener('DOMContentLoaded', async function () { document.addEventListener('DOMContentLoaded', async function () {
const {email, password, name} = await window.electronAPI.readUserConfig('user_info'); const {id: userId, name, email, departmentId: userDepartmentId} = await window.databaseAPI.getUserInfo();
const emailInput = document.querySelector('input[name="email"]'); const emailInput = document.querySelector('input[name="email"]');
const usernameInput = document.querySelector('input[name="username"]'); const usernameInput = document.querySelector('input[name="username"]');
const passwordInput = document.querySelector('input[name="password"]'); const passwordInput = document.querySelector('input[name="password"]');
// Pre-fill form fields with existing data // Pre-fill form fields with existing data
emailInput.value = email; id = userId
departmentId = userDepartmentId;
usernameInput.value = name; usernameInput.value = name;
passwordInput.value = password; emailInput.value = email;
passwordInput.value = '';
const backButton = document.getElementById('back'); const backButton = document.getElementById('back');
const submitButton = document.getElementById('submit'); const submitButton = document.getElementById('submit');
// Get operation codes from the backend // Get operation codes from the backend
const operationCodes = await window.electronAPI.getOperationsCodes(); const operationCodes = await window.networkAPI.getOperationsCodes();
if (!operationCodes) { if (!operationCodes) {
await window.electronAPI.showAlert('Internal error of the application.'); await window.uiAPI.showAlert('Internal error of the application.');
return; return;
} }
@@ -39,8 +44,7 @@ document.addEventListener('DOMContentLoaded', async function () {
const name = usernameInput.value; const name = usernameInput.value;
const password = passwordInput.value; const password = passwordInput.value;
const { id, departmentId } = await window.electronAPI.readUserConfig('user_info'); // Fetch login data const app_type = await window.databaseAPI.getAppType();
const app_type = await window.electronAPI.readUserConfig('app_type'); // Fetch app type
// Prepare the data to be sent via the UC socket // Prepare the data to be sent via the UC socket
const messageData = { const messageData = {
@@ -49,18 +53,18 @@ document.addEventListener('DOMContentLoaded', async function () {
email: email, email: email,
password: password, password: password,
departmentId: departmentId, departmentId: departmentId,
app_type: app_type // Include app_type in the payload app_type: app_type
}; };
// Open UC socket // Open UC socket
if (!await window.electronAPI.openUcSocket()) { if (!await window.networkAPI.openUcSocket()) {
await window.electronAPI.showAlert('Failed to open socket. Internal error of the application.'); await window.uiAPI.showAlert('Failed to open socket. Internal error of the application.');
return; return;
} }
// Send the message to the server using the UC socket // Send the message to the server using the UC socket
if (!await window.electronAPI.sendUcMessage(codeModifyUser, messageData)) { if (!await window.networkAPI.sendUcMessage(codeModifyUser, messageData)) {
await window.electronAPI.showAlert('Failed to send message.'); await window.uiAPI.showAlert('Failed to send message.');
return; return;
} }
@@ -70,8 +74,7 @@ document.addEventListener('DOMContentLoaded', async function () {
console.log('User update successful.'); console.log('User update successful.');
// Save updated user info to the userConfig // Save updated user info to the userConfig
await window.electronAPI.writeUserConfig('user_credentials', { email: email, password: password }); await window.databaseAPI.writeUserInfo({ id: id, email: email, name: name, departmentId: departmentId });
await window.electronAPI.writeUserConfig('user_info', {id: id, departmentId: departmentId, name: name});
// Navigate back to the main menu // Navigate back to the main menu
fadeOut('main_menu'); fadeOut('main_menu');
+3 -1
View File
@@ -1,4 +1,6 @@
document.addEventListener('DOMContentLoaded', async function () { document.addEventListener('DOMContentLoaded', async function () {
await new Promise(resolve => setTimeout(resolve, 7000)); await new Promise(resolve => setTimeout(resolve, 7000));
window.electronAPI.changeContent('login'); await window.databaseAPI.resetInternalDatabase();
await window.uiAPI.stopWorkers();
await window.uiAPI.changeContent('welcome');
}); });
+12 -12
View File
@@ -6,9 +6,9 @@ document.addEventListener('DOMContentLoaded', async function () {
const resetPasswordButton = document.getElementById('resetPassword'); const resetPasswordButton = document.getElementById('resetPassword');
// Retrieve the operation codes via IPC // Retrieve the operation codes via IPC
const operationCodes = await window.electronAPI.getOperationsCodes(); const operationCodes = await window.networkAPI.getOperationsCodes();
if (!operationCodes) { if (!operationCodes) {
await window.electronAPI.showAlert('Internal error of the application.'); await window.uiAPI.showAlert('Internal error of the application.');
return; return;
} }
@@ -18,14 +18,14 @@ document.addEventListener('DOMContentLoaded', async function () {
// Back to login // Back to login
backToLoginButton.addEventListener('click', function (e) { backToLoginButton.addEventListener('click', function (e) {
e.preventDefault(); e.preventDefault();
window.electronAPI.changeContent('login'); window.uiAPI.changeContent('login');
}); });
// Reset password logic // Reset password logic
resetPasswordButton.addEventListener('click', async function (e) { resetPasswordButton.addEventListener('click', async function (e) {
e.preventDefault(); e.preventDefault();
if (!await window.electronAPI.openUcSocket()) { if (!await window.networkAPI.openUcSocket()) {
await window.electronAPI.showAlert('Internal error of the application.'); await window.uiAPI.showAlert('Internal error of the application.');
return; return;
} }
@@ -36,7 +36,7 @@ document.addEventListener('DOMContentLoaded', async function () {
// Ensure the email and new password are provided // Ensure the email and new password are provided
if (!email || !newPassword) { if (!email || !newPassword) {
await window.electronAPI.showAlert('Please provide both email and new password.'); await window.uiAPI.showAlert('Please provide both email and new password.');
return; return;
} }
@@ -44,23 +44,23 @@ document.addEventListener('DOMContentLoaded', async function () {
return; return;
} }
await window.electronAPI.closeUcSocket(); await window.networkAPI.closeUcSocket();
await window.electronAPI.showAlert('Password reset successfully.'); await window.uiAPI.showAlert('Password reset successfully.');
window.electronAPI.changeContent('login'); await window.uiAPI.changeContent('login');
}); });
}); });
async function attemptResetPassword(email, newPassword) { async function attemptResetPassword(email, newPassword) {
const app_type = await window.electronAPI.readUserConfig('app_type'); const app_type = await window.databaseAPI.getAppType();
const messageData = { email, newPassword, app_type }; const messageData = { email, newPassword, app_type };
if (!await window.electronAPI.sendUcMessage(codeResetPassword, messageData)) return false; if (!await window.networkAPI.sendUcMessage(codeResetPassword, messageData)) return false;
const response = await waitForResponse(); const response = await waitForResponse();
if (response && response.operationCode === codeOk) { if (response && response.operationCode === codeOk) {
return true; return true;
} else { } else {
await window.electronAPI.showAlert(response?.metaInfo?.message || 'Error resetting password.'); await window.uiAPI.showAlert(response?.metaInfo?.message || 'Error resetting password.');
return false; return false;
} }
} }
+10 -16
View File
@@ -9,7 +9,7 @@ document.addEventListener('DOMContentLoaded', async function () {
selectFileButton.addEventListener('click', async function (event) { selectFileButton.addEventListener('click', async function (event) {
event.preventDefault(); event.preventDefault();
try { try {
pathToFile = await window.electronAPI.selectFile(); pathToFile = await window.uiAPI.selectFile();
updateFileName(); updateFileName();
} catch (error) { } catch (error) {
console.error('Error opening file dialog:', error); console.error('Error opening file dialog:', error);
@@ -20,11 +20,11 @@ document.addEventListener('DOMContentLoaded', async function () {
// Check if a file was chosen // Check if a file was chosen
if (!pathToFile.trim()) { if (!pathToFile.trim()) {
await window.electronAPI.showAlert('File not chosen!'); await window.uiAPI.showAlert('File not chosen!');
return; return;
} }
const user_info = await window.electronAPI.readUserConfig('user_info'); const user_info = await window.databaseAPI.getUserInfo();
if (!user_info) { if (!user_info) {
return; return;
} }
@@ -36,7 +36,7 @@ document.addEventListener('DOMContentLoaded', async function () {
.map(checkbox => checkbox.value); // Get IP of the selected users .map(checkbox => checkbox.value); // Get IP of the selected users
if (!selectedUserIps.length) { if (!selectedUserIps.length) {
await window.electronAPI.showAlert('No user selected!'); await window.uiAPI.showAlert('No user selected!');
return; return;
} }
@@ -48,8 +48,8 @@ document.addEventListener('DOMContentLoaded', async function () {
path: pathToFile, // File path path: pathToFile, // File path
userName: user_info.name // Sender's username from userConfig userName: user_info.name // Sender's username from userConfig
}; };
await window.electronAPI.addTaskToSendFileQueue(task); await window.databaseAPI.addTaskToSendFileQueue(task);
await window.electronAPI.showAlert('File sent to the queue.'); await window.uiAPI.showAlert('File sent to the queue.');
console.log(`Task added to send file to IP ${selectedUserIp}`); console.log(`Task added to send file to IP ${selectedUserIp}`);
} catch (error) { } catch (error) {
console.error(`Error processing IP ${selectedUserIp}:`, error); console.error(`Error processing IP ${selectedUserIp}:`, error);
@@ -85,18 +85,12 @@ async function fetchUsersAndCreateCheckboxes() {
.map(checkbox => checkbox.value) .map(checkbox => checkbox.value)
); );
const usersInfoId = await window.electronAPI.readApplicationInfo('active_users_info'); const usersInfo = await window.databaseAPI.getActiveUsers();
if (!usersInfoId) {
await window.electronAPI.showAlert('Internal error.');
return;
}
const usersInfo = await window.electronAPI.readMemoryEntry(usersInfoId);
if (!usersInfo || usersInfo.length === 0) { if (!usersInfo || usersInfo.length === 0) {
await window.electronAPI.showAlert('No active users found.'); await window.uiAPI.showAlert('No active users found.');
clearInterval(fetchUsersInterval); clearInterval(fetchUsersInterval);
fetchUsersInterval = null; fetchUsersInterval = null;
await window.electronAPI.changeContent('main_menu'); await window.uiAPI.changeContent('main_menu');
return; return;
} }
@@ -115,7 +109,7 @@ async function fetchUsersAndCreateCheckboxes() {
} }
const label = document.createElement('label'); const label = document.createElement('label');
label.innerHTML = `${user.user_info.name}`; // Display user's name label.innerHTML = `${user.user_info.name}`;
label.insertBefore(checkbox, label.firstChild); label.insertBefore(checkbox, label.firstChild);
usersDiv.appendChild(label); usersDiv.appendChild(label);
+11 -11
View File
@@ -18,12 +18,12 @@ document.addEventListener('DOMContentLoaded', async function () {
const step1 = document.getElementById('step1'); const step1 = document.getElementById('step1');
const step2 = document.getElementById('step2'); const step2 = document.getElementById('step2');
if (!await window.electronAPI.openUcSocket()) { if (!await window.networkAPI.openUcSocket()) {
alert('Internal error of the application.'); alert('Internal error of the application.');
return; return;
} }
const operationCodes = await window.electronAPI.getOperationsCodes(); const operationCodes = await window.networkAPI.getOperationsCodes();
if (!operationCodes) { if (!operationCodes) {
await window.electronAPI.showAlert('Internal error of the application.'); await window.electronAPI.showAlert('Internal error of the application.');
return; return;
@@ -35,7 +35,7 @@ document.addEventListener('DOMContentLoaded', async function () {
let departments = await getDepartments(); let departments = await getDepartments();
if (departments === null) { if (departments === null) {
await window.electronAPI.showAlert('Failed to fetch departments.'); await window.uiAPI.showAlert('Failed to fetch departments.');
return; return;
} }
@@ -47,7 +47,7 @@ document.addEventListener('DOMContentLoaded', async function () {
userData.email = document.querySelector('input[name="email"]').value; userData.email = document.querySelector('input[name="email"]').value;
userData.name = document.querySelector('input[name="name"]').value; userData.name = document.querySelector('input[name="name"]').value;
userData.password = document.querySelector('input[name="password"]').value; userData.password = document.querySelector('input[name="password"]').value;
userData.app_type = await window.electronAPI.readUserConfig('app_type'); userData.app_type = await window.databaseAPI.getAppType();
// Move to Step 2 // Move to Step 2
const departmentList = document.getElementById('departmentList'); const departmentList = document.getElementById('departmentList');
@@ -75,7 +75,7 @@ document.addEventListener('DOMContentLoaded', async function () {
// Get the selected department // Get the selected department
userData.departmentId = document.querySelector('input[name="dept"]:checked')?.value; userData.departmentId = document.querySelector('input[name="dept"]:checked')?.value;
if (!userData.departmentId) { if (!userData.departmentId) {
await window.electronAPI.showAlert('Please select a department.'); await window.uiAPI.showAlert('Please select a department.');
return; return;
} }
@@ -84,8 +84,8 @@ document.addEventListener('DOMContentLoaded', async function () {
return; return;
} }
await window.electronAPI.showAlert('Signup successful!'); await window.uiAPI.showAlert('Signup successful!');
await window.electronAPI.changeContent('login'); await window.uiAPI.changeContent('login');
}); });
// Back to Step 1 from Step 2 // Back to Step 1 from Step 2
@@ -96,8 +96,8 @@ document.addEventListener('DOMContentLoaded', async function () {
// Back to login // Back to login
backToLogin.addEventListener('click', async function (e) { backToLogin.addEventListener('click', async function (e) {
e.preventDefault(); e.preventDefault();
await window.electronAPI.closeUcSocket(); await window.networkAPI.closeUcSocket();
await window.electronAPI.changeContent('login'); await window.uiAPI.changeContent('login');
}); });
}); });
@@ -113,13 +113,13 @@ async function getDepartments() {
} }
async function attemptSignUp() { async function attemptSignUp() {
if (!await window.electronAPI.sendUcMessage(codeSignUp, userData)) return false; if (!await window.networkAPI.sendUcMessage(codeSignUp, userData)) return false;
const response = await waitForResponse(); const response = await waitForResponse();
if (response && response.operationCode === codeOk) { if (response && response.operationCode === codeOk) {
return true; return true;
} }
await window.electronAPI.showAlert(`Signup failed: ${response?.metaInfo?.message || 'Unknown error'}`); await window.uiAPI.showAlert(`Signup failed: ${response?.metaInfo?.message || 'Unknown error'}`);
return false; return false;
} }
+141
View File
@@ -0,0 +1,141 @@
import jsonfile from 'jsonfile'
import { promises as fs } from 'fs'
import { v4 as uuidv4 } from 'uuid'
import { MemoryManager } from './helpers/memory_manager'
import { QueueManager, FileItemTask } from './helpers/queue_manager'
const defaultData = {
app_config: {
app_type: 'client',
user_info: {
id: '',
email: '',
password: '',
departmentId: '',
name: '',
},
encryption_key: {
key: '',
iv: '',
},
reset_application_preferences: false,
logged_in: false,
server_found: false,
},
network: {
usersInLan: [],
serverIp: '',
},
local_resources: {
directory_schemes: {
backup: { id: uuidv4(), path: '', structure: {}, totalSize: 0 },
department: { id: uuidv4(), path: '', structure: {}, totalSize: 0 },
shared: { id: uuidv4(), path: '', structure: {}, totalSize: 0 },
},
},
}
export class JsonDatabase<T, Q> {
private readonly filePath: string
private memoryStore: MemoryManager<Q>
private queueStore: QueueManager
constructor(filePath: string) {
this.filePath = filePath
this.memoryStore = new MemoryManager<Q>()
this.queueStore = new QueueManager()
// Ensure the file exists and is not empty
this.ensureFileExists().then(() => {
console.log('[Database] File exists and is properly initialized.')
})
}
// Ensure the JSON file exists, or create it with default content
private async ensureFileExists(): Promise<void> {
try {
await fs.access(this.filePath) // Check if file exists
// Check if file is empty
const fileContent = await fs.readFile(this.filePath, 'utf-8')
if (!fileContent.trim()) {
console.log(`[Database] ${this.filePath} is empty. Writing default data...`)
await jsonfile.writeFile(this.filePath, defaultData, { spaces: 2 })
}
} catch (error) {
console.log(`[Database] ${this.filePath} not found. Creating with default data...`)
// Create the file with default content
await jsonfile.writeFile(this.filePath, defaultData, { spaces: 2 })
console.log(`[Database] ${this.filePath} created successfully.`)
}
}
async reset(): Promise<void> {
await jsonfile.writeFile(this.filePath, defaultData, { spaces: 2 })
}
// Read JSON from file
async read(): Promise<T> {
try {
return await jsonfile.readFile(this.filePath)
} catch (error: any) {
if (error.code === 'ENOENT') {
return defaultData as T // Return defaultData if file does not exist
}
throw error
}
}
// Update JSON file with atomic write
async update(
updateCallback: (data: Awaited<T>) => Awaited<T> | Promise<Awaited<T>>,
): Promise<void> {
let data = await this.read()
data = await updateCallback(data)
await jsonfile.writeFile(this.filePath, data, { spaces: 2 })
}
// Generate and return a new UUID
generateUUID(): string {
return uuidv4()
}
// MemoryStore Operations
setMemory(uuid: string, value: Q): void {
this.memoryStore.set(uuid, value)
}
getMemory(uuid: string): Q | undefined {
return this.memoryStore.get(uuid)
}
deleteMemory(uuid: string): boolean {
return this.memoryStore.delete(uuid)
}
hasMemory(uuid: string): boolean {
return this.memoryStore.has(uuid)
}
// Queue Operations
pushQueue(task: FileItemTask): void {
this.queueStore.push(task)
}
popQueue(): FileItemTask | undefined {
return this.queueStore.pop()
}
seekQueue(): FileItemTask | undefined {
return this.queueStore.seek()
}
queueSize(): number {
return this.queueStore.size()
}
clearQueue(): void {
this.queueStore.clear()
}
}
@@ -0,0 +1,42 @@
export class MemoryManager<T> {
private storage: Map<string, T>
constructor() {
this.storage = new Map<string, T>()
}
// Set a value in the memory store
set(uuid: string, value: T): void {
this.storage.set(uuid, value)
}
// Get a value from the memory store
get(uuid: string): T | undefined {
return this.storage.get(uuid)
}
// Check if a key exists
has(uuid: string): boolean {
return this.storage.has(uuid)
}
// Delete a key-value pair
delete(uuid: string): boolean {
return this.storage.delete(uuid)
}
// Get all keys
keys(): string[] {
return Array.from(this.storage.keys())
}
// Get all values
values(): T[] {
return Array.from(this.storage.values())
}
// Clear all stored values
clear(): void {
this.storage.clear()
}
}
@@ -0,0 +1,38 @@
export interface FileItemTask {
ip: string
path: string
userName: string
}
export class QueueManager {
private queue: FileItemTask[]
constructor() {
this.queue = []
}
// Add a new task to the queue
push(task: FileItemTask): void {
this.queue.push(task)
}
// Remove and return the first task (FIFO)
pop(): FileItemTask | undefined {
return this.queue.shift()
}
// View the first task without removing it
seek(): FileItemTask | undefined {
return this.queue[0]
}
// Get queue length
size(): number {
return this.queue.length
}
// Clear all tasks
clear(): void {
this.queue = []
}
}
@@ -0,0 +1,20 @@
export interface AppConfigScheme {
app_type: 'client' | 'ceo'
user_info: UserInfoScheme
encryption_key: EncryptionKeyScheme
reset_application_preferences: boolean
logged_in: boolean
server_found: boolean
}
export interface UserInfoScheme {
id: string
name: string
email: string
departmentId: string
}
export interface EncryptionKeyScheme {
key: string
iv: string
}
@@ -0,0 +1,9 @@
import { NetworkScheme } from './network_scheme'
import { AppConfigScheme } from './app_config_scheme'
import { LocalResourcesScheme } from './local_resources_scheme'
export interface DatabaseScheme {
app_config: AppConfigScheme
network: NetworkScheme
local_resources: LocalResourcesScheme
}
@@ -0,0 +1,16 @@
export interface LocalResourcesScheme {
directory_schemes: DirectorySchemes
}
export interface DirectorySchemes {
backup: DirectoryInfo
department: DirectoryInfo
shared: DirectoryInfo
}
export interface DirectoryInfo {
id: string
path: string
structure: any
totalSize: number
}
@@ -0,0 +1,12 @@
export interface NetworkUserScheme {
id: string
ip: string
name: string
departmentId: string
}
export interface NetworkScheme {
serverIp: string
usersInLan: NetworkUserScheme[]
announcement: string
}
+89 -106
View File
@@ -1,192 +1,175 @@
import fs from 'fs'; import fs from 'fs'
import path from 'path'; import path from 'path'
import { FileEncryptor } from './file_encryptor'; import { FileEncryptor } from './file_encryptor'
import { MemoryManager } from './memory_manager'; import { TcpCommunicator } from './tcp_communicator'
import { JsonManager } from './json_manager'; import { operationCodes } from '../network/operation_codes'
import { TcpCommunicator } from './tcp_communicator'; import { ParsedMessage } from '../network/message_handler'
import { operationCodes } from '../network/operation_codes'; import { JsonDatabase } from '../database/database'
import { ParsedMessage } from "../network/message_handler"; import { DatabaseScheme } from '../database/schemes/database_scheme'
import { NetworkUserScheme } from '../database/schemes/network_scheme'
export class BackupManager { export class BackupManager {
private fileEncryptor: FileEncryptor | null = null; private fileEncryptor: FileEncryptor | null = null
private memoryManager: MemoryManager; private readonly db: JsonDatabase<DatabaseScheme, any>
private applicationInfo: JsonManager; private readonly clientPort: number
private userConfig: JsonManager; private isBusy: boolean = false
private readonly clientPort: number; private intervalId: NodeJS.Timeout | null = null
private isBusy: boolean = false; private stopRequested: boolean = false
private intervalId: NodeJS.Timeout | null = null;
private stopRequested: boolean = false;
constructor(userConfigPath: string, applicationInfoPath: string, memoryManagerPath: string, clientPort: number) { constructor(pathToDatabaseFile: string, clientPort: number) {
this.applicationInfo = new JsonManager(applicationInfoPath); this.db = new JsonDatabase(pathToDatabaseFile)
this.userConfig = new JsonManager(userConfigPath); this.clientPort = clientPort
this.memoryManager = new MemoryManager(memoryManagerPath);
this.clientPort = clientPort;
} }
async start(): Promise<void> { async start(): Promise<void> {
this.intervalId = setInterval(async () => { this.intervalId = setInterval(async () => {
if (!this.isBusy || !this.stopRequested) { if (!this.isBusy || !this.stopRequested) {
this.isBusy = true; this.isBusy = true
this.log('Start successfully. Backup files to users.'); this.log('Start successfully. Backup files to users.')
await this.initialize(); await this.initialize()
} }
if (global.gc) { if (global.gc) {
global.gc(); global.gc()
} }
}, 10000); // 10-second interval for testing }, 10000) // 10-second interval for testing
} }
private async initialize(): Promise<void> { private async initialize(): Promise<void> {
this.isBusy = true; this.isBusy = true
try { try {
const encryptionKeyData = await this.userConfig.readValue('encryption_key'); const data = await this.db.read()
if (!encryptionKeyData || !encryptionKeyData.key || !encryptionKeyData.iv) { const userInfo = data.app_config.user_info
this.log('Encryption key data is missing in user configuration.', 'error'); const encryptionKey = data.app_config.encryption_key
return; const usersIp = data.network.usersInLan.map((user: NetworkUserScheme) => user.ip)
} const backupDirectoryData = data.local_resources.directory_schemes.backup
this.fileEncryptor = new FileEncryptor(encryptionKeyData.key, encryptionKeyData.iv); this.fileEncryptor = new FileEncryptor(encryptionKey.key, encryptionKey.iv)
const userInfo = await this.userConfig.readValue('user_info'); await this.sendFilesToUsers(
if (!userInfo || !userInfo.name) { userInfo.name,
this.log('User information is missing in user configuration.', 'error'); usersIp,
return; backupDirectoryData.structure,
} backupDirectoryData.path,
const userName = userInfo.name; )
const backupDirectoryData = await this.applicationInfo.readValue('backupDirectory');
if (!backupDirectoryData || !backupDirectoryData.id || !backupDirectoryData.path) {
this.log('Backup directory information is missing in application info.', 'error');
return;
}
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
if (!activeUsersIp || !activeUsersIp.length) {
this.log('No active users found.', 'error');
return;
}
const backupDirectoryId = backupDirectoryData.id;
const backupDirectoryPath = backupDirectoryData.path;
const directoryData = await this.memoryManager.retrieveMetaInformation(backupDirectoryId);
if (!directoryData || !directoryData.structure) {
this.log('Backup directory structure is missing in memory.', 'error');
return;
}
await this.sendFilesToUsers(directoryData.structure, userName, activeUsersIp, backupDirectoryPath);
} catch (error: any) { } catch (error: any) {
this.log(`Error in initialize process: ${error.message}`, 'error'); this.log(`Error in initialize process: ${error.message}`, 'error')
} finally { } finally {
this.log('Backup process completed.'); this.log('Backup process completed.')
this.isBusy = false; this.isBusy = false
} }
} }
private encryptFile(filePath: string): string { private encryptFile(filePath: string): string {
if (!this.fileEncryptor) { if (!this.fileEncryptor) {
return filePath; return filePath
} }
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
this.log(`File not found: ${filePath}`, 'error'); this.log(`File not found: ${filePath}`, 'error')
return ''; return ''
} }
return this.fileEncryptor.encryptFileToBase64(filePath); return this.fileEncryptor.encryptFileToBase64(filePath)
} }
private async sendFilesToUsers(fileStructure: { [key: string]: string }, userName: string, usersIp: string[], backupDirectoryPath: string): Promise<void> { private async sendFilesToUsers(
let unsentFiles = Object.keys(fileStructure); userName: string,
usersIp: string[],
fileStructure: { [key: string]: string },
backupDirectoryPath: string,
): Promise<void> {
let unsentFiles = Object.keys(fileStructure)
for (const fileName of unsentFiles) { for (const fileName of unsentFiles) {
const filePath = fileStructure[fileName]; const filePath = fileStructure[fileName]
const encryptedFileContent = this.encryptFile(filePath); const encryptedFileContent = this.encryptFile(filePath)
if (!encryptedFileContent) { if (!encryptedFileContent) {
this.log(`Failed to encrypt file: ${fileName}`, 'error'); this.log(`Failed to encrypt file: ${fileName}`, 'error')
continue; continue
} }
const relativeFilePath = path.relative(backupDirectoryPath, filePath); const relativeFilePath = path.relative(backupDirectoryPath, filePath)
const metaInfo = { userName, relativeFilePath }; const metaInfo = { userName, relativeFilePath }
for (const ip of usersIp) { for (const ip of usersIp) {
const tcpCommunicator = new TcpCommunicator(ip, this.clientPort); const tcpCommunicator = new TcpCommunicator(ip, this.clientPort)
try { try {
await tcpCommunicator.connect(); await tcpCommunicator.connect()
this.log(`Connected to ${ip}`); this.log(`Connected to ${ip}`)
const sendSuccess = await tcpCommunicator.sendMessage(operationCodes.BACKUP_FILE, metaInfo, Buffer.from(encryptedFileContent, 'base64')); const sendSuccess = await tcpCommunicator.sendMessage(
operationCodes.BACKUP_FILE,
metaInfo,
Buffer.from(encryptedFileContent, 'base64'),
)
if (!sendSuccess) { if (!sendSuccess) {
throw new Error('Failed to send file content.'); throw new Error('Failed to send file content.')
} }
const responseReceived = await this.waitForResponse(tcpCommunicator); const responseReceived = await this.waitForResponse(tcpCommunicator)
if (!responseReceived) { if (!responseReceived) {
throw new Error('Timeout waiting for the message response.'); throw new Error('Timeout waiting for the message response.')
} }
this.log(`Successfully sent file: ${fileName} to ${ip}`); this.log(`Successfully sent file: ${fileName} to ${ip}`)
unsentFiles = unsentFiles.filter(f => f !== fileName); unsentFiles = unsentFiles.filter((f) => f !== fileName)
break; break
} catch (error) { } catch (error) {
this.log(`Failed to send file: ${fileName} to ${ip}. Error: ${error}`, 'error'); this.log(`Failed to send file: ${fileName} to ${ip}. Error: ${error}`, 'error')
} finally { } finally {
await tcpCommunicator.disconnect(); await tcpCommunicator.disconnect()
this.log(`Disconnected from ${ip}`); this.log(`Disconnected from ${ip}`)
} }
} }
} }
if (unsentFiles.length > 0) { if (unsentFiles.length > 0) {
process.send?.({type: 'log', message: 'Backup could not be completed for all files'}); process.send?.({ type: 'log', message: 'Backup could not be completed for all files' })
} else { } else {
process.send?.({type: 'log', message: 'Backup completed successfully' }); process.send?.({ type: 'log', message: 'Backup completed successfully' })
} }
} }
private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise<ParsedMessage | null> { private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise<ParsedMessage | null> {
return new Promise((resolve) => { return new Promise((resolve) => {
const idResponseCheck = setInterval(() => { const idResponseCheck = setInterval(() => {
if (!tcpCommunicator) return null; if (!tcpCommunicator) return null
if (tcpCommunicator.hasResponseArrived()) { if (tcpCommunicator.hasResponseArrived()) {
clearInterval(idResponseCheck); clearInterval(idResponseCheck)
resolve(tcpCommunicator.getLastResult()); resolve(tcpCommunicator.getLastResult())
} }
}, 100); }, 100)
}); })
} }
async stop(): Promise<void> { async stop(): Promise<void> {
this.stopRequested = true; // Signal that stop is requested this.stopRequested = true // Signal that stop is requested
if (this.intervalId) { if (this.intervalId) {
clearInterval(this.intervalId); clearInterval(this.intervalId)
this.intervalId = null; this.intervalId = null
} }
// Wait for any ongoing process to complete if busy // Wait for any ongoing process to complete if busy
while (this.isBusy) { while (this.isBusy) {
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100))
} }
console.log("[BackupManager] Stopped successfully."); console.log('[BackupManager] Stopped successfully.')
} }
// Unified logging function // Unified logging function
private log(message: string, level: 'log' | 'error' | 'warn' = 'log'): void { private log(message: string, level: 'log' | 'error' | 'warn' = 'log'): void {
const prefix = '[BackupManager]'; const prefix = '[BackupManager]'
if (level === 'error') { if (level === 'error') {
console.error(`${prefix} ${message}`); console.error(`${prefix} ${message}`)
} else if (level === 'warn') { } else if (level === 'warn') {
console.warn(`${prefix} ${message}`); console.warn(`${prefix} ${message}`)
} else { } else {
console.log(`${prefix} ${message}`); console.log(`${prefix} ${message}`)
} }
} }
} }
+116 -112
View File
@@ -1,206 +1,210 @@
import { JsonManager } from './json_manager'; import { TcpCommunicator } from './tcp_communicator'
import { TcpCommunicator } from "./tcp_communicator"; import { operationCodes } from '../network/operation_codes'
import { operationCodes } from '../network/operation_codes'; import path from 'path'
import path from 'path'; import fs from 'fs'
import fs from 'fs'; import crypto from 'crypto'
import crypto from 'crypto'; import { ParsedMessage } from '../network/message_handler'
import { ParsedMessage } from "../network/message_handler"; import { JsonDatabase } from '../database/database'
import { DatabaseScheme } from '../database/schemes/database_scheme'
export class BackupRetrievalWorker { export class BackupRetrievalWorker {
private userConfig: JsonManager; private db: JsonDatabase<DatabaseScheme, any>
private applicationInfo: JsonManager; private readonly clientPort: number
private readonly clientPort: number; private readonly destinationPath: string
private readonly destinationPath: string; private encryptionKey: Buffer | null = null
private encryptionKey: Buffer | null = null; private iv: Buffer | null = null
private iv: Buffer | null = null; private tcpCommunicator: TcpCommunicator | null = null
private tcpCommunicator: TcpCommunicator | null = null; private stopRequested: boolean = false
private stopRequested: boolean = false; private isBusy: boolean = false
private isBusy: boolean = false;
constructor(userConfigPath: string, applicationInfoPath: string, clientPort: number, destinationPath: string) { constructor(pathToDatabaseFile: string, clientPort: number, destinationPath: string) {
this.userConfig = new JsonManager(userConfigPath); this.db = new JsonDatabase<DatabaseScheme, any>(pathToDatabaseFile)
this.applicationInfo = new JsonManager(applicationInfoPath); this.clientPort = clientPort
this.clientPort = clientPort; this.destinationPath = destinationPath
this.destinationPath = destinationPath;
} }
// Logging helper function // Logging helper function
private log(message: string, level: 'log' | 'error' = 'log'): void { private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[BackupRetrievalWorker]'; const prefix = '[BackupRetrievalWorker]'
if (level === 'error') { if (level === 'error') {
console.error(`${prefix} ${message}`); console.error(`${prefix} ${message}`)
} else { } else {
console.log(`${prefix} ${message}`); console.log(`${prefix} ${message}`)
} }
} }
async start(): Promise<void> { async start(): Promise<void> {
if(!this.stopRequested) return; if (!this.stopRequested) return
this.isBusy = true; this.isBusy = true
try { try {
const userInfo = await this.userConfig.readValue('user_info'); const data = await this.db.read()
if (!userInfo || !userInfo.name) { const userInfo = data.app_config.user_info
throw new Error('User information or name is missing.'); const encryptionKey = data.app_config.encryption_key
}
const userName = userInfo.name;
const encryptionData = await this.userConfig.readValue('encryption_key'); const userName = userInfo.name
if (!encryptionData || !encryptionData.key || !encryptionData.iv) { this.encryptionKey = Buffer.from(encryptionKey.key, 'base64')
throw new Error('Encryption key or IV is missing.'); this.iv = Buffer.from(encryptionKey.iv, 'base64')
const activeUsers = data.network.usersInLan
if (!activeUsers.length) {
throw new Error('No active users found.')
} }
this.encryptionKey = Buffer.from(encryptionData.key, 'base64'); for (const lanUser of activeUsers) {
this.iv = Buffer.from(encryptionData.iv, 'base64'); const success = await this.processBackupForIp(lanUser.ip, userName)
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
if (!activeUsersIp || !activeUsersIp.length) {
throw new Error('No active users found.');
}
for (const ip of activeUsersIp) {
const success = await this.processBackupForIp(ip, userName);
if (!success) { if (!success) {
throw new Error(`Failed to retrieve backup from ${ip}`); throw new Error(`Failed to retrieve backup from ${lanUser.ip}`)
} }
this.log(`Backup retrieved successfully from ${ip}`); this.log(`Backup retrieved successfully from ${lanUser.ip}`)
} }
process.send?.({ type: 'showAlert', message: 'Backup retrieval completed successfully.' }); process.send?.({ type: 'showAlert', message: 'Backup retrieval completed successfully.' })
process.send?.({ type: 'changeContent', page: 'main_menu' }); process.send?.({ type: 'changeContent', page: 'main_menu' })
} catch (error: any) { } catch (error: any) {
this.log(`Error in BackupRetrievalWorker: ${error.message}`, 'error'); this.log(`Error in BackupRetrievalWorker: ${error.message}`, 'error')
process.send?.({ type: 'showAlert', message: `A problem occurred: ${error.message}` }); process.send?.({ type: 'showAlert', message: `A problem occurred: ${error.message}` })
process.send?.({ type: 'changeContent', page: 'main_menu' }); process.send?.({ type: 'changeContent', page: 'main_menu' })
} } finally {
finally { this.isBusy = false
this.isBusy = false;
} }
if (global.gc) { if (global.gc) {
global.gc(); global.gc()
} }
} }
private async processBackupForIp(ip: string, userName: string): Promise<boolean> { private async processBackupForIp(ip: string, userName: string): Promise<boolean> {
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort); this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort)
if (!await this.tcpCommunicator.connect()) { if (!(await this.tcpCommunicator.connect())) {
this.log(`Failed to connect to ${ip}`, 'error'); this.log(`Failed to connect to ${ip}`, 'error')
return true; return true
} }
const backupExists = await this.checkIfBackupExists(userName); const backupExists = await this.checkIfBackupExists(userName)
if (!backupExists) { if (!backupExists) {
this.log(`No backup found for user ${userName} on IP ${ip}`); this.log(`No backup found for user ${userName} on IP ${ip}`)
await this.tcpCommunicator.disconnect(); await this.tcpCommunicator.disconnect()
return true; return true
} }
const backupStructure = await this.requestBackupStructure(userName); const backupStructure = await this.requestBackupStructure(userName)
if (!backupStructure || Object.keys(backupStructure).length === 0) { if (!backupStructure || Object.keys(backupStructure).length === 0) {
this.log(`No files found in backup structure for user ${userName} on IP ${ip}`); this.log(`No files found in backup structure for user ${userName} on IP ${ip}`)
await this.tcpCommunicator.disconnect(); await this.tcpCommunicator.disconnect()
return true; return true
} }
for (const relativeFilePath of Object.keys(backupStructure)) { for (const relativeFilePath of Object.keys(backupStructure)) {
const fileRequestSuccess = await this.requestBackupFile(userName, relativeFilePath); const fileRequestSuccess = await this.requestBackupFile(userName, relativeFilePath)
if (!fileRequestSuccess) { if (!fileRequestSuccess) {
await this.tcpCommunicator.disconnect(); await this.tcpCommunicator.disconnect()
throw new Error(`Failed to retrieve file ${relativeFilePath} from backup for user ${userName} on IP ${ip}`); throw new Error(
`Failed to retrieve file ${relativeFilePath} from backup for user ${userName} on IP ${ip}`,
)
} }
} }
await this.tcpCommunicator.disconnect(); await this.tcpCommunicator.disconnect()
return true; return true
} }
private async checkIfBackupExists(userName: string): Promise<boolean> { private async checkIfBackupExists(userName: string): Promise<boolean> {
if (!this.tcpCommunicator) return false; if (!this.tcpCommunicator) return false
const metaInfo = { name: userName }; const metaInfo = { name: userName }
if (!await this.tcpCommunicator.sendMessage(operationCodes.IS_BACKUP_CREATED, metaInfo)) return false; if (!(await this.tcpCommunicator.sendMessage(operationCodes.IS_BACKUP_CREATED, metaInfo)))
return false
const response = await this.waitForResponse(); const response = await this.waitForResponse()
return response?.metaInfo?.backupExists === true; return response?.metaInfo?.backupExists === true
} }
private async requestBackupStructure(userName: string): Promise<any> { private async requestBackupStructure(userName: string): Promise<any> {
if (!this.tcpCommunicator) return false; if (!this.tcpCommunicator) return false
const metaInfo = { name: userName }; const metaInfo = { name: userName }
if (!await this.tcpCommunicator.sendMessage(operationCodes.GET_BACKUP_STRUCTURE, metaInfo)) return null; if (!(await this.tcpCommunicator.sendMessage(operationCodes.GET_BACKUP_STRUCTURE, metaInfo)))
return null
const response = await this.waitForResponse(); const response = await this.waitForResponse()
return response?.metaInfo?.structure || null; return response?.metaInfo?.structure || null
} }
private async requestBackupFile(userName: string, relativeFilePath: string): Promise<boolean> { private async requestBackupFile(userName: string, relativeFilePath: string): Promise<boolean> {
if (!this.tcpCommunicator) return false; if (!this.tcpCommunicator) return false
const metaInfo = { name: userName, relativeFilePath }; const metaInfo = { name: userName, relativeFilePath }
if (!await this.tcpCommunicator.sendMessage(operationCodes.REQ_FILE_FROM_BACKUP, metaInfo)) return false; if (!(await this.tcpCommunicator.sendMessage(operationCodes.REQ_FILE_FROM_BACKUP, metaInfo)))
return false
const response = await this.waitForResponse(); const response = await this.waitForResponse()
if (response?.operationCode === operationCodes.OK && response.metaInfo && response.fileContent) { if (
return this.saveFile(response.metaInfo.relativeFilePath, response.fileContent.toString('base64')); response?.operationCode === operationCodes.OK &&
response.metaInfo &&
response.fileContent
) {
return this.saveFile(
response.metaInfo.relativeFilePath,
response.fileContent.toString('base64'),
)
} }
return false; return false
} }
private saveFile(relativeFilePath: string, fileContent: string): boolean { private saveFile(relativeFilePath: string, fileContent: string): boolean {
if (!this.encryptionKey || !this.iv) { if (!this.encryptionKey || !this.iv) {
throw new Error('Encryption key or IV is not set.'); throw new Error('Encryption key or IV is not set.')
} }
let encryptedBuffer: Buffer; let encryptedBuffer: Buffer
try { try {
encryptedBuffer = Buffer.from(fileContent, 'base64'); encryptedBuffer = Buffer.from(fileContent, 'base64')
} catch (error: any) { } catch (error: any) {
throw new Error(`Error decoding base64 file content: ${error.message}`); throw new Error(`Error decoding base64 file content: ${error.message}`)
} }
let decryptedContent: Buffer; let decryptedContent: Buffer
try { try {
const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv); const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv)
decryptedContent = Buffer.concat([decipher.update(encryptedBuffer), decipher.final()]); decryptedContent = Buffer.concat([decipher.update(encryptedBuffer), decipher.final()])
} catch (error: any) { } catch (error: any) {
throw new Error(`Error decrypting file: ${error.message}`); throw new Error(`Error decrypting file: ${error.message}`)
} }
const fullFilePath = path.join(this.destinationPath, relativeFilePath); const fullFilePath = path.join(this.destinationPath, relativeFilePath)
try { try {
const dirPath = path.dirname(fullFilePath); const dirPath = path.dirname(fullFilePath)
if (!fs.existsSync(dirPath)) { if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true }); fs.mkdirSync(dirPath, { recursive: true })
} }
fs.writeFileSync(fullFilePath, decryptedContent); fs.writeFileSync(fullFilePath, decryptedContent)
this.log(`File saved successfully: ${fullFilePath}`); this.log(`File saved successfully: ${fullFilePath}`)
return true; return true
} catch (error: any) { } catch (error: any) {
throw new Error(`Error saving file ${relativeFilePath}: ${error.message}`); throw new Error(`Error saving file ${relativeFilePath}: ${error.message}`)
} }
} }
async stop(): Promise<void> { async stop(): Promise<void> {
this.stopRequested = true; // Signal that stop is requested this.stopRequested = true // Signal that stop is requested
// Wait for any ongoing process to complete if busy // Wait for any ongoing process to complete if busy
while (this.isBusy) { while (this.isBusy) {
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100))
} }
console.log("[BackupManager] Stopped successfully."); console.log('[BackupManager] Stopped successfully.')
} }
private async waitForResponse(): Promise<ParsedMessage | null> { private async waitForResponse(): Promise<ParsedMessage | null> {
return new Promise((resolve) => { return new Promise((resolve) => {
const idResponseCheck = setInterval(async () => { const idResponseCheck = setInterval(async () => {
if (!this.tcpCommunicator) return null; if (!this.tcpCommunicator) return null
if (this.tcpCommunicator.hasResponseArrived()) { if (this.tcpCommunicator.hasResponseArrived()) {
clearInterval(idResponseCheck); clearInterval(idResponseCheck)
resolve(this.tcpCommunicator.getLastResult()); resolve(this.tcpCommunicator.getLastResult())
} }
}, 100); }, 100)
}); })
} }
} }
+98 -121
View File
@@ -1,214 +1,191 @@
import fs from 'fs'; import fs from 'fs'
import path from 'path'; import path from 'path'
import { TcpCommunicator } from './tcp_communicator'; import { TcpCommunicator } from './tcp_communicator'
import { operationCodes } from '../network/operation_codes'; import { operationCodes } from '../network/operation_codes'
import { JsonManager } from './json_manager'; import { ParsedMessage } from '../network/message_handler'
import { MemoryManager } from './memory_manager'; import { JsonDatabase } from '../database/database'
import { ParsedMessage } from "../network/message_handler"; import { DatabaseScheme } from '../database/schemes/database_scheme'
export class DepartmentSharer { export class DepartmentSharer {
private userConfig: JsonManager; private readonly db: JsonDatabase<DatabaseScheme, any>
private applicationInfo: JsonManager; private departmentDirectory: string | null = null
private memoryManager: MemoryManager; private readonly clientPort: number
private departmentDirectory: string | null; private isBusy: boolean = false
private readonly clientPort: number; private tcpCommunicator: TcpCommunicator | null = null
private isBusy: boolean = false; private stopRequested: boolean = false
private tcpCommunicator: TcpCommunicator | null = null; private intervalId: NodeJS.Timeout | null = null
private stopRequested: boolean = false;
private intervalId: NodeJS.Timeout | null = null;
constructor( constructor(pathToDatabaseFile: string, clientPort: number) {
userConfigPath: string, this.db = new JsonDatabase(pathToDatabaseFile)
applicationInfoPath: string, this.clientPort = clientPort
memoryManagerPath: string,
clientPort: number
) {
this.userConfig = new JsonManager(userConfigPath);
this.applicationInfo = new JsonManager(applicationInfoPath);
this.memoryManager = new MemoryManager(memoryManagerPath);
this.clientPort = clientPort;
this.departmentDirectory = null;
} }
// Start sharing files with the department every minute // Start sharing files with the department every minute
async start(): Promise<void> { async start(): Promise<void> {
this.intervalId = setInterval(async () => { this.intervalId = setInterval(async () => {
if (!this.isBusy || !this.stopRequested) { if (!this.isBusy || !this.stopRequested) {
this.isBusy = true; this.isBusy = true
this.log('Start successfully. Sharing files with the department.'); this.log('Start successfully. Sharing files with the department.')
await this.shareFilesWithDepartment(); await this.shareFilesWithDepartment()
} }
if (global.gc) { if (global.gc) {
global.gc(); global.gc()
} }
}, 10000); // 10-second interval for testing }, 10000) // 10-second interval for testing
} }
// Share files with users in the same department // Share files with users in the same department
private async shareFilesWithDepartment(): Promise<void> { private async shareFilesWithDepartment(): Promise<void> {
try { try {
// Get the current user's department information const data = await this.db.read()
const userInfo = await this.userConfig.readValue('user_info'); const userInfo = data.app_config.user_info
if (!userInfo || !userInfo.departmentId || !userInfo.name) { const departmentStructure = data.local_resources.directory_schemes.department
throw new Error('User information or department ID is missing in the configuration.');
}
const departmentId = userInfo.departmentId; const departmentId = userInfo.departmentId
const userName = userInfo.name; const userName = userInfo.name
const activeUsers = data.network.usersInLan
// Get the list of active users from applicationInfo this.departmentDirectory = departmentStructure.path
const activeUsersId = await this.applicationInfo.readValue('active_users_info');
if (!activeUsersId) {
throw new Error('No active users found.');
}
const activeUsers = await this.memoryManager.retrieveMetaInformation(activeUsersId);
if (!activeUsers || activeUsers.length === 0) {
throw new Error('No active users found.');
}
// Filter users who belong to the same department // Filter users who belong to the same department
const departmentUsers = activeUsers.filter((user: any) => user.user_info.departmentId === departmentId); const departmentUsers = activeUsers.filter(
(user: any) => user.user_info.departmentId === departmentId,
)
if (departmentUsers.length === 0) { if (departmentUsers.length === 0) {
throw new Error('No users found in the same department.'); throw new Error('No users found in the same department.')
}
// Get department directory info
const departmentData = await this.applicationInfo.readValue('departmentDirectory');
if (!departmentData || !departmentData.path || !departmentData.id) {
throw new Error('No department directory found.');
}
this.departmentDirectory = departmentData.path;
// Read files from the MemoryManager related to this department
const departmentFiles = await this.memoryManager.retrieveMetaInformation(departmentData.id);
if (!departmentFiles || !departmentFiles.structure) {
throw new Error('No files found for this department in the memory manager.');
} }
// Iterate over all department users and perform the operations // Iterate over all department users and perform the operations
for (const user of departmentUsers) { for (const user of departmentUsers) {
const userIp = user.ip; const userIp = user.ip
this.tcpCommunicator = new TcpCommunicator(userIp, this.clientPort); this.tcpCommunicator = new TcpCommunicator(userIp, this.clientPort)
if (!await this.tcpCommunicator.connect()) continue; if (!(await this.tcpCommunicator.connect())) continue
// First clear the department directory; // First clear the department directory;
if (await this.clearDepartmentDirectory(userName)) { if (await this.clearDepartmentDirectory(userName)) {
await this.sendFilesToUser(departmentFiles.structure, userName); await this.sendFilesToUser(departmentStructure.structure, userName)
} }
await this.tcpCommunicator.disconnect(); await this.tcpCommunicator.disconnect()
} }
} } catch (error: any) {
catch(error: any) { this.log(error.message, 'error')
this.log(error.message, 'error'); } finally {
} this.log('Department sharing completed.')
finally{ this.isBusy = false
this.log('Department sharing completed.');
this.isBusy = false;
} }
} }
// Clear the department directory for a user // Clear the department directory for a user
private async clearDepartmentDirectory(userName: string): Promise<boolean> { private async clearDepartmentDirectory(userName: string): Promise<boolean> {
if(!this.tcpCommunicator) return false; if (!this.tcpCommunicator) return false
if (!await this.tcpCommunicator.sendMessage(operationCodes.CLEAR_DEPARTMENT, {userName: userName})) return false; if (
const response = await this.waitForResponse(); !(await this.tcpCommunicator.sendMessage(operationCodes.CLEAR_DEPARTMENT, {
userName: userName,
}))
)
return false
const response = await this.waitForResponse()
if (!response || response.operationCode !== operationCodes.OK) { if (!response || response.operationCode !== operationCodes.OK) {
this.log('Failed to clear the department directory.', 'error'); this.log('Failed to clear the department directory.', 'error')
return false; return false
} }
return true; return true
} }
// Send the files to a user in the department // Send the files to a user in the department
private async sendFilesToUser(files: { [key: string]: string }, userName: string): Promise<void> { private async sendFilesToUser(files: { [key: string]: string }, userName: string): Promise<void> {
if(!this.tcpCommunicator) return; if (!this.tcpCommunicator) return
const unsentFiles = Object.keys(files); const unsentFiles = Object.keys(files)
console.log(`\n\n${unsentFiles}\n\n`); console.log(`\n\n${unsentFiles}\n\n`)
for (const fileName of unsentFiles) { for (const fileName of unsentFiles) {
const filePath = files[fileName]; const filePath = files[fileName]
// Ensure the file exists before attempting to send // Ensure the file exists before attempting to send
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
this.log(`File not found: ${filePath}`, 'error'); this.log(`File not found: ${filePath}`, 'error')
continue; continue
} }
// Read the file content // Read the file content
const fileContent = fs.readFileSync(filePath); const fileContent = fs.readFileSync(filePath)
// Get the relative path of the file (used in the meta info) // Get the relative path of the file (used in the meta info)
if (!this.departmentDirectory) return; if (!this.departmentDirectory) return
const relativeFilePath = path.relative(this.departmentDirectory, filePath); const relativeFilePath = path.relative(this.departmentDirectory, filePath)
// Prepare the metaInfo (same structure as FileSharer) // Prepare the metaInfo (same structure as FileSharer)
const metaInfo = { const metaInfo = {
userName, userName,
relativeFilePath relativeFilePath,
};
// Send the file
if (!await this.tcpCommunicator.sendMessage(operationCodes.DEPARTMENT_FILE, metaInfo, Buffer.from(fileContent))) return;
console.log(`\n\nSending file: ${fileName} to ${userName}\n\n`);
const response = await this.waitForResponse();
if (!response || response.operationCode !== operationCodes.OK) {
this.log(`Failed to send file: ${fileName}`, 'error');
return;
} }
this.log(`File sent successfully: ${fileName} to ${userName}`); // Send the file
if (
!(await this.tcpCommunicator.sendMessage(
operationCodes.DEPARTMENT_FILE,
metaInfo,
Buffer.from(fileContent),
))
)
return
unsentFiles.splice(unsentFiles.indexOf(fileName), 1); console.log(`\n\nSending file: ${fileName} to ${userName}\n\n`)
await this.tcpCommunicator.disconnect();
const response = await this.waitForResponse()
if (!response || response.operationCode !== operationCodes.OK) {
this.log(`Failed to send file: ${fileName}`, 'error')
return
}
this.log(`File sent successfully: ${fileName} to ${userName}`)
unsentFiles.splice(unsentFiles.indexOf(fileName), 1)
await this.tcpCommunicator.disconnect()
} }
} }
async stop(): Promise<void> { async stop(): Promise<void> {
this.stopRequested = true; // Signal that stop is requested this.stopRequested = true // Signal that stop is requested
if (this.intervalId) { if (this.intervalId) {
clearInterval(this.intervalId); clearInterval(this.intervalId)
this.intervalId = null; this.intervalId = null
} }
// Wait for any ongoing process to complete if busy // Wait for any ongoing process to complete if busy
while (this.isBusy) { while (this.isBusy) {
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100))
} }
console.log("[BackupManager] Stopped successfully."); console.log('[BackupManager] Stopped successfully.')
} }
private async waitForResponse(): Promise<ParsedMessage | null> { private async waitForResponse(): Promise<ParsedMessage | null> {
return new Promise((resolve) => { return new Promise((resolve) => {
const idResponseCheck = setInterval(async () => { const idResponseCheck = setInterval(async () => {
if (!this.tcpCommunicator) return null; if (!this.tcpCommunicator) return null
if (this.tcpCommunicator.hasResponseArrived()) { if (this.tcpCommunicator.hasResponseArrived()) {
clearInterval(idResponseCheck); clearInterval(idResponseCheck)
resolve(this.tcpCommunicator.getLastResult()); resolve(this.tcpCommunicator.getLastResult())
} }
}, 100); }, 100)
}); })
} }
// Unified logging function // Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void { private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[DepartmentSharer]'; const prefix = '[DepartmentSharer]'
if (level === 'error') { if (level === 'error') {
console.error(`${prefix} ${message}`); console.error(`${prefix} ${message}`)
} else { } else {
console.log(`${prefix} ${message}`); console.log(`${prefix} ${message}`)
} }
} }
} }
+95 -124
View File
@@ -1,163 +1,134 @@
import { promises as fs, watch, FSWatcher } from 'fs'; import { promises as fs, watch, FSWatcher } from 'fs'
import path from 'path'; import path from 'path'
import { MemoryManager } from './memory_manager'; import { JsonDatabase } from '../database/database'
import { JsonManager } from './json_manager'; import { DatabaseScheme } from '../database/schemes/database_scheme'
export class DirectoryWatcher { export class DirectoryWatcher {
private directoryPath: string; private readonly watchers: Map<string, FSWatcher> // Stores watchers with directory ID as the key
private directoryMemoryId: string; private readonly db: JsonDatabase<DatabaseScheme, any>
private directoryScheme: any; private totalSizes: Map<string, number> // Stores total sizes per directory ID
private applicationInfo: JsonManager;
private memoryManager: MemoryManager;
private readonly sourceKey: string;
private directoryWatcher: FSWatcher | null;
private totalSize: number;
private isBusy: boolean;
constructor(memoryManagerPath: string, applicationInfoPath: string, sourceKey: string) { constructor(pathToDatabaseFile: string) {
this.sourceKey = sourceKey; this.db = new JsonDatabase<DatabaseScheme, any>(pathToDatabaseFile)
this.applicationInfo = new JsonManager(applicationInfoPath); this.watchers = new Map<string, FSWatcher>()
this.memoryManager = new MemoryManager(memoryManagerPath); this.totalSizes = new Map<string, number>()
this.directoryMemoryId = '';
this.directoryPath = '';
this.directoryWatcher = null;
this.totalSize = 0;
this.isBusy = false;
} }
// Start the watcher with a busy flag to prevent overlapping operations // Start the watcher and register all directories
async start(): Promise<void> { async start(): Promise<void> {
setInterval(async () => { const dbData = await this.db.read()
if (!this.isBusy) { const directorySchemes = dbData.local_resources?.directory_schemes
this.isBusy = true;
const initialized = await this.initialize(); if (!directorySchemes) {
if (initialized) this.log('Directory watcher started successfully.'); this.log('Error: directory_schemes not found in database.', 'error')
this.isBusy = false; return
}
}, 10000); // 10-second interval for testing
} }
// Method to initialize and validate the backup directory // Initialize watchers for each directory
async initialize(): Promise<boolean> { this.registerWatcher(directorySchemes.backup?.id, directorySchemes.backup?.path)
const directoryData = await this.applicationInfo.readValue(this.sourceKey); this.registerWatcher(directorySchemes.department?.id, directorySchemes.department?.path)
if (!directoryData) { this.registerWatcher(directorySchemes.shared?.id, directorySchemes.shared?.path)
this.log('Directory data not found in application info.', 'error');
return false; this.log('Directory watcher started successfully.')
} }
this.directoryPath = directoryData.path; // Register a watcher for a directory with a given ID
this.directoryMemoryId = directoryData.id; private registerWatcher(id: string | undefined, directoryPath: string | undefined): void {
if (!id || !directoryPath) {
if (!this.directoryMemoryId || !this.directoryPath) { this.log(`Skipping watcher: ID or path is missing.`, 'error')
this.log('Components of entry in \'DirectoryWatcher\' not found.', 'error'); return
await this.applicationInfo.removeValue(this.sourceKey);
return false;
} }
if (!this.directoryScheme || Object.keys(this.directoryScheme).length === 0) { if (this.watchers.has(id)) {
// No structure in memory, scan and save it this.log(`Watcher for ID ${id} is already running.`, 'error')
const result = await this.buildDirectoryScheme(this.directoryPath); return
this.directoryScheme = result.structure;
this.totalSize = result.size;
await this.memoryManager.updateMetaInformation(this.directoryMemoryId, {
structure: this.directoryScheme,
totalSize: this.totalSize,
});
} }
// Start watching the directory (after stopping any existing watcher) // Create and store a new watcher
this.restartWatcher(); const watcher = watch(directoryPath, { recursive: true }, async (eventType, filename) => {
return true; if (filename) {
this.log(`File change detected in ${directoryPath}: ${eventType} - ${filename}`)
await this.handleDirectoryChange(id, directoryPath)
}
})
this.watchers.set(id, watcher)
this.log(`Watching directory: ${directoryPath} (ID: ${id})`)
} }
// Recursively build the directory structure and calculate the total size // Handle directory change and update the correct entry in the database
private async buildDirectoryScheme(dirPath: string): Promise<{ structure: any, size: number }> { private async handleDirectoryChange(id: string, directoryPath: string): Promise<void> {
const directoryScheme: any = {}; const result = await this.buildDirectoryScheme(directoryPath)
let totalSize = 0; this.totalSizes.set(id, result.size)
const items = await fs.readdir(dirPath, { withFileTypes: true }); await this.db.update((data) => {
// @ts-ignore
if (!data.local_resources || !data.local_resources.directory_schemes[id]) {
this.log(`Error: Directory scheme for ID ${id} not found in database.`, 'error')
return data
}
// @ts-ignore
data.local_resources.directory_schemes[id].structure = result.structure
// @ts-ignore
data.local_resources.directory_schemes[id].totalSize = result.size
return data
})
this.log(`Updated directory scheme for ID: ${id}`)
}
// Recursively build the directory structure and calculate total size
private async buildDirectoryScheme(dirPath: string): Promise<{ structure: any; size: number }> {
const directoryScheme: any = {}
let totalSize = 0
const items = await fs.readdir(dirPath, { withFileTypes: true })
for (const item of items) { for (const item of items) {
const fullPath = path.join(dirPath, item.name); const fullPath = path.join(dirPath, item.name)
const stats = await fs.stat(fullPath); const stats = await fs.stat(fullPath)
if (item.isDirectory()) { if (item.isDirectory()) {
// If it's a directory, recursively build its structure and accumulate size const { structure, size } = await this.buildDirectoryScheme(fullPath)
const { structure, size } = await this.buildDirectoryScheme(fullPath); directoryScheme[item.name] = structure
directoryScheme[item.name] = structure; totalSize += size
totalSize += size;
} else if (item.isFile()) { } else if (item.isFile()) {
// If it's a file, store its full path and accumulate size directoryScheme[item.name] = fullPath
directoryScheme[item.name] = fullPath; totalSize += stats.size
totalSize += stats.size;
} }
} }
return { structure: directoryScheme, size: totalSize }; return { structure: directoryScheme, size: totalSize }
} }
// Restart the directory watcher, ensuring any previous watcher is closed // Stop and remove a watcher for a specific directory ID
private restartWatcher(): void { public stopWatcher(id: string): void {
if (this.directoryWatcher) { const watcher = this.watchers.get(id)
this.log('Stopping existing watcher...'); if (watcher) {
this.directoryWatcher.close(); watcher.close()
this.watchers.delete(id)
this.log(`Stopped watcher for ID: ${id}`)
}
} }
this.startDirectoryWatcher(); // Stop all watchers
} public stopAllWatchers(): void {
for (const [id, watcher] of this.watchers) {
// Start watching the backup directory for changes watcher.close()
private startDirectoryWatcher(): void { this.log(`Stopped watcher for ID: ${id}`)
if (!this.directoryPath) {
throw new Error('Backup directory not set. Cannot start watcher.');
}
this.directoryWatcher = watch(this.directoryPath, { recursive: true }, async (eventType, filename) => {
if (filename) {
this.log(`File change detected: ${eventType} - ${filename}`);
// Rebuild the directory scheme and update memory
const result = await this.buildDirectoryScheme(this.directoryPath);
this.directoryScheme = result.structure;
this.totalSize = result.size;
await this.memoryManager.updateMetaInformation(this.directoryMemoryId, {
structure: this.directoryScheme,
totalSize: this.totalSize,
});
this.log('Directory structure and size updated in memory.');
}
});
this.log(`Watching for changes in: ${this.directoryPath}`);
}
// Close the directory watcher
public closeWatcher(): void {
if (this.directoryWatcher) {
this.log(`Stopping watcher for ${this.directoryPath}`);
this.directoryWatcher.close();
this.directoryWatcher = null;
}
if (global.gc) {
global.gc();
} }
this.watchers.clear()
} }
// Unified logging function // Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void { private log(message: string, level: 'log' | 'error' = 'log'): void {
const sourcePrefix = `[DirectoryWatcher] {${this.capitalize(this.sourceKey)}}`; const sourcePrefix = `[DirectoryWatcher]`
if (level === 'error') { if (level === 'error') {
console.error(`${sourcePrefix} ${message}`); console.error(`${sourcePrefix} ${message}`)
} else { } else {
console.log(`${sourcePrefix} ${message}`); console.log(`${sourcePrefix} ${message}`)
} }
} }
// Capitalize the first letter of the sourceKey
private capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
} }
+20 -20
View File
@@ -1,34 +1,34 @@
import fs from 'fs'; import fs from 'fs'
import crypto from 'crypto'; import crypto from 'crypto'
export class FileEncryptor { export class FileEncryptor {
private readonly encryptionKey: Buffer; private readonly encryptionKey: Buffer
private readonly iv: Buffer; private readonly iv: Buffer
constructor(base64Key: string, base64Iv: string) { constructor(base64Key: string, base64Iv: string) {
// Decode the base64-encoded key and IV // Decode the base64-encoded key and IV
this.encryptionKey = Buffer.from(base64Key, 'base64'); this.encryptionKey = Buffer.from(base64Key, 'base64')
this.iv = Buffer.from(base64Iv, 'base64'); this.iv = Buffer.from(base64Iv, 'base64')
} }
// Method to read a file, encrypt it, and return the encrypted content as a base64 string // Method to read a file, encrypt it, and return the encrypted content as a base64 string
public encryptFileToBase64(filePath: string): string { public encryptFileToBase64(filePath: string): string {
try { try {
// Read the file contents // Read the file contents
const fileBuffer = fs.readFileSync(filePath); const fileBuffer = fs.readFileSync(filePath)
// Create the cipher using AES-256-CBC (or another algorithm you prefer) // Create the cipher using AES-256-CBC (or another algorithm you prefer)
const cipher = crypto.createCipheriv('aes-256-cbc', this.encryptionKey, this.iv); const cipher = crypto.createCipheriv('aes-256-cbc', this.encryptionKey, this.iv)
// Encrypt the file data // Encrypt the file data
let encryptedData = cipher.update(fileBuffer); let encryptedData = cipher.update(fileBuffer)
encryptedData = Buffer.concat([encryptedData, cipher.final()]); encryptedData = Buffer.concat([encryptedData, cipher.final()])
// Return the encrypted data as a base64 string // Return the encrypted data as a base64 string
return encryptedData.toString('base64'); return encryptedData.toString('base64')
} catch (err) { } catch (err) {
console.error(`Error encrypting file at path ${filePath}:`, err); console.error(`Error encrypting file at path ${filePath}:`, err)
throw err; throw err
} }
} }
@@ -36,20 +36,20 @@ export class FileEncryptor {
public decryptBase64(encryptedBase64: string): Buffer { public decryptBase64(encryptedBase64: string): Buffer {
try { try {
// Decode the base64-encoded encrypted data // Decode the base64-encoded encrypted data
const encryptedData = Buffer.from(encryptedBase64, 'base64'); const encryptedData = Buffer.from(encryptedBase64, 'base64')
// Create the decipher using AES-256-CBC // Create the decipher using AES-256-CBC
const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv); const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv)
// Decrypt the data // Decrypt the data
let decryptedData = decipher.update(encryptedData); let decryptedData = decipher.update(encryptedData)
decryptedData = Buffer.concat([decryptedData, decipher.final()]); decryptedData = Buffer.concat([decryptedData, decipher.final()])
// Return the decrypted buffer // Return the decrypted buffer
return decryptedData; return decryptedData
} catch (err) { } catch (err) {
console.error('Error decrypting data:', err); console.error('Error decrypting data:', err)
throw err; throw err
} }
} }
} }
+70 -70
View File
@@ -1,146 +1,146 @@
import fs from "fs"; import fs from 'fs'
import path from "path"; import path from 'path'
import { QueueManager } from './queue_manager'; import { TcpCommunicator } from './tcp_communicator'
import { TcpCommunicator } from "./tcp_communicator"; import { operationCodes } from '../network/operation_codes'
import { operationCodes } from '../network/operation_codes'; import { ParsedMessage } from '../network/message_handler'
import { compareFnFileItemTask, FileItemTask } from "../interfaces/file_item_task"; import { JsonDatabase } from '../database/database'
import { ParsedMessage } from "../network/message_handler"; import { DatabaseScheme } from '../database/schemes/database_scheme'
interface FileSendTask { interface FileSendTask {
ip: string; ip: string
path: string; path: string
userName: string; userName: string
} }
export class FileSharer { export class FileSharer {
private queueManager: QueueManager<FileSendTask>; private readonly db: JsonDatabase<DatabaseScheme, any>
private readonly clientPort: number; private readonly clientPort: number
private isBusy: boolean; private isBusy: boolean = false
private tcpCommunicator: TcpCommunicator | null = null; private tcpCommunicator: TcpCommunicator | null = null
private stopRequested: boolean = false; private stopRequested: boolean = false
private intervalId: NodeJS.Timeout | null = null; private intervalId: NodeJS.Timeout | null = null
constructor(queueFilePath: string, clientPort: number) { constructor(pathToDatabaseFile: string, clientPort: number) {
this.queueManager = new QueueManager<FileItemTask>(queueFilePath, compareFnFileItemTask); this.db = new JsonDatabase(pathToDatabaseFile)
this.clientPort = clientPort; this.clientPort = clientPort
this.isBusy = false; // Initialize the busy flag
} }
// Start processing the file queue // Start processing the file queue
async start(): Promise<void> { async start(): Promise<void> {
this.intervalId = setInterval(async () => { this.intervalId = setInterval(async () => {
if (!this.isBusy || !this.stopRequested) { // Check if the queue is already being processed if (!this.isBusy || !this.stopRequested) {
this.isBusy = true; // Set busy flag to true before starting // Check if the queue is already being processed
this.log("Start successfully. Processing the queue."); this.isBusy = true // Set busy flag to true before starting
await this.processQueue(); this.log('Start successfully. Processing the queue.')
await this.processQueue()
} }
if (global.gc) { if (global.gc) {
global.gc(); global.gc()
} }
}, 10000); // 10 seconds interval }, 10000)
} }
// Method to process the queue // Method to process the queue
private async processQueue(): Promise<void> { private async processQueue(): Promise<void> {
while (!this.queueManager.isEmpty()) { for (let i = 0; i < this.db.queueSize(); i++) {
const task = this.queueManager.peek(); const task = this.db.popQueue()
this.log('trimiti fisier');
if (task) { if (task) {
this.log(`Processing task for file: ${task.path} to IP: ${task.ip}`); this.log(`Processing task for file: ${task.path} to IP: ${task.ip}`)
const success = await this.sendFile(task); const success = await this.sendFile(task)
if (!success) { if (!success) {
this.log(`Failed to send file: ${task.path} to IP: ${task.ip}. Re-adding to queue.`, 'error'); this.log(
this.queueManager.dequeue(); `Failed to send file: ${task.path} to IP: ${task.ip}. Re-adding to queue.`,
this.queueManager.enqueue(task); // Re-add to queue if failed 'error',
)
this.db.pushQueue(task)
} else { } else {
this.log(`File sent successfully: ${task.path} to IP: ${task.ip}`); this.log(`File sent successfully: ${task.path} to IP: ${task.ip}`)
this.queueManager.dequeue();
} }
} }
} }
this.isBusy = false; // Reset busy flag after the queue is processed this.isBusy = false // Reset busy flag after the queue is processed
} }
// Method to send the file to a specific IP using TcpCommunicator // Method to send the file to a specific IP using TcpCommunicator
private async sendFile(task: FileSendTask): Promise<boolean> { private async sendFile(task: FileSendTask): Promise<boolean> {
const { ip, path: filePath, userName } = task; const { ip, path: filePath, userName } = task
// Ensure the file exists before attempting to send // Ensure the file exists before attempting to send
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
this.log(`File not found: ${filePath}`, 'error'); this.log(`File not found: ${filePath}`, 'error')
return false; return false
} }
// Read the file contents // Read the file contents
const fileContent = fs.readFileSync(filePath); const fileContent = fs.readFileSync(filePath)
// Extract the file name from the file path using path.basename // Extract the file name from the file path using path.basename
const fileName = path.basename(filePath); const fileName = path.basename(filePath)
const metaInfo = { const metaInfo = {
userName, // Sender's username userName, // Sender's username
relativeFilePath: fileName, // Use the file name instead of the full path relativeFilePath: fileName, // Use the file name instead of the full path
};
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort);
if (!await this.tcpCommunicator.connect()) {
this.log(`Failed to connect to IP: ${ip}`, 'error');
return false;
} }
this.log(`Sending file: ${filePath} to IP: ${ip}`); this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort)
if (!await this.tcpCommunicator.sendMessage(operationCodes.SHARE_FILE, metaInfo, fileContent)) return false; if (!(await this.tcpCommunicator.connect())) {
this.log(`Failed to connect to IP: ${ip}`, 'error')
return false
}
const response = await this.waitForResponse(); this.log(`Sending file: ${filePath} to IP: ${ip}`)
if (!(await this.tcpCommunicator.sendMessage(operationCodes.SHARE_FILE, metaInfo, fileContent)))
return false
const response = await this.waitForResponse()
if (!response || response.operationCode !== operationCodes.OK) { if (!response || response.operationCode !== operationCodes.OK) {
this.log(`Failed to send file: ${filePath} to IP: ${ip}`, 'error'); this.log(`Failed to send file: ${filePath} to IP: ${ip}`, 'error')
return false; return false
} }
await this.tcpCommunicator.disconnect(); await this.tcpCommunicator.disconnect()
return true; return true
} }
async stop(): Promise<void> { async stop(): Promise<void> {
this.stopRequested = true; // Signal that stop is requested this.stopRequested = true // Signal that stop is requested
if (this.intervalId) { if (this.intervalId) {
clearInterval(this.intervalId); clearInterval(this.intervalId)
this.intervalId = null; this.intervalId = null
} }
// Wait for any ongoing process to complete if busy // Wait for any ongoing process to complete if busy
while (this.isBusy) { while (this.isBusy) {
await new Promise((resolve) => setTimeout(resolve, 100)); await new Promise((resolve) => setTimeout(resolve, 100))
} }
console.log("[BackupManager] Stopped successfully."); console.log('[BackupManager] Stopped successfully.')
} }
private async waitForResponse(): Promise<ParsedMessage | null> { private async waitForResponse(): Promise<ParsedMessage | null> {
return new Promise((resolve) => { return new Promise((resolve) => {
const idResponseCheck = setInterval(async () => { const idResponseCheck = setInterval(async () => {
if (!this.tcpCommunicator) return null; if (!this.tcpCommunicator) return null
if (this.tcpCommunicator.hasResponseArrived()) { if (this.tcpCommunicator.hasResponseArrived()) {
clearInterval(idResponseCheck); clearInterval(idResponseCheck)
resolve(this.tcpCommunicator.getLastResult()); // Resolve the response or null if not available resolve(this.tcpCommunicator.getLastResult()) // Resolve the response or null if not available
} }
}, 100); // Check every 100 milliseconds if the response has arrived }, 100) // Check every 100 milliseconds if the response has arrived
}); })
} }
// Unified logging function // Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void { private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[FileSharer]'; const prefix = '[FileSharer]'
if (level === 'error') { if (level === 'error') {
console.error(`${prefix} ${message}`); console.error(`${prefix} ${message}`)
} else { } else {
console.log(`${prefix} ${message}`); console.log(`${prefix} ${message}`)
} }
} }
} }
+44 -44
View File
@@ -1,24 +1,24 @@
import fs from 'fs'; import fs from 'fs'
import path from 'path'; import path from 'path'
export class JsonManager { export class JsonManager {
private readonly filePath: string; private readonly filePath: string
private readonly lockFilePath: string; private readonly lockFilePath: string
constructor(filePath: string) { constructor(filePath: string) {
const dir = path.dirname(filePath); const dir = path.dirname(filePath)
// Check if the directory exists, throw error if it doesn't // Check if the directory exists, throw error if it doesn't
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
throw new Error(`The directory does not exist: ${dir}`); throw new Error(`The directory does not exist: ${dir}`)
} }
this.filePath = filePath; this.filePath = filePath
this.lockFilePath = `${filePath}.lock`; // Define the lock file path this.lockFilePath = `${filePath}.lock` // Define the lock file path
// If the file doesn't exist, create it // If the file doesn't exist, create it
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, JSON.stringify({}, null, 2), 'utf8'); fs.writeFileSync(filePath, JSON.stringify({}, null, 2), 'utf8')
} }
} }
@@ -26,94 +26,94 @@ export class JsonManager {
private async acquireLock(): Promise<void> { private async acquireLock(): Promise<void> {
while (fs.existsSync(this.lockFilePath)) { while (fs.existsSync(this.lockFilePath)) {
// Wait until the lock file is released // Wait until the lock file is released
await new Promise(resolve => setTimeout(resolve, 100)); // 100ms delay before retrying await new Promise((resolve) => setTimeout(resolve, 100)) // 100ms delay before retrying
} }
// Create the lock file // Create the lock file
fs.writeFileSync(this.lockFilePath, ''); fs.writeFileSync(this.lockFilePath, '')
} }
// Method to release the lock (delete .lock file) // Method to release the lock (delete .lock file)
private releaseLock(): void { private releaseLock(): void {
if (fs.existsSync(this.lockFilePath)) { if (fs.existsSync(this.lockFilePath)) {
fs.unlinkSync(this.lockFilePath); fs.unlinkSync(this.lockFilePath)
} }
} }
// Read a value by key from the JSON file with a lock // Read a value by key from the JSON file with a lock
public async readValue(key: string): Promise<any | null> { public async readValue(key: string): Promise<any | null> {
await this.acquireLock(); // Acquire the lock await this.acquireLock() // Acquire the lock
try { try {
if (!fs.existsSync(this.filePath)) return null; if (!fs.existsSync(this.filePath)) return null
const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8')); const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'))
return data[key] !== undefined ? data[key] : null; return data[key] !== undefined ? data[key] : null
} catch (err: any) { } catch (err: any) {
console.error(`Error reading from JSON file: ${err.message}`); console.error(`Error reading from JSON file: ${err.message}`)
return null; return null
} finally { } finally {
this.releaseLock(); // Always release the lock after the operation this.releaseLock() // Always release the lock after the operation
} }
} }
// Write a key-value pair to the JSON file with a lock // Write a key-value pair to the JSON file with a lock
public async writeValue(key: string, value: any): Promise<boolean> { public async writeValue(key: string, value: any): Promise<boolean> {
await this.acquireLock(); // Acquire the lock await this.acquireLock() // Acquire the lock
try { try {
let data: { [key: string]: any } = {}; let data: { [key: string]: any } = {}
if (fs.existsSync(this.filePath)) { if (fs.existsSync(this.filePath)) {
data = JSON.parse(fs.readFileSync(this.filePath, 'utf8')); data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'))
} }
// Update the key with the new value // Update the key with the new value
data[key] = value; data[key] = value
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8'); fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8')
return true; return true
} catch (err: any) { } catch (err: any) {
console.error(`Error writing to JSON file: ${err.message}`); console.error(`Error writing to JSON file: ${err.message}`)
return false; return false
} finally { } finally {
this.releaseLock(); // Always release the lock after the operation this.releaseLock() // Always release the lock after the operation
} }
} }
// Remove a key-value pair from the JSON file with a lock // Remove a key-value pair from the JSON file with a lock
public async removeValue(key: string): Promise<boolean> { public async removeValue(key: string): Promise<boolean> {
await this.acquireLock(); // Acquire the lock await this.acquireLock() // Acquire the lock
try { try {
if (!fs.existsSync(this.filePath)) return false; if (!fs.existsSync(this.filePath)) return false
const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8')); const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'))
if (data[key] !== undefined) { if (data[key] !== undefined) {
delete data[key]; delete data[key]
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8'); fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8')
return true; return true
} }
return false; return false
} catch (err: any) { } catch (err: any) {
console.error(`Error removing key from JSON file: ${err.message}`); console.error(`Error removing key from JSON file: ${err.message}`)
return false; return false
} finally { } finally {
this.releaseLock(); // Always release the lock after the operation this.releaseLock() // Always release the lock after the operation
} }
} }
// Reset the JSON file by clearing all data with a lock // Reset the JSON file by clearing all data with a lock
public async resetFile(): Promise<boolean> { public async resetFile(): Promise<boolean> {
await this.acquireLock(); // Acquire the lock await this.acquireLock() // Acquire the lock
try { try {
fs.writeFileSync(this.filePath, JSON.stringify({}, null, 2), 'utf8'); fs.writeFileSync(this.filePath, JSON.stringify({}, null, 2), 'utf8')
return true; return true
} catch (err: any) { } catch (err: any) {
console.error(`Error resetting JSON file: ${err.message}`); console.error(`Error resetting JSON file: ${err.message}`)
return false; return false
} finally { } finally {
this.releaseLock(); // Always release the lock after the operation this.releaseLock() // Always release the lock after the operation
} }
} }
} }
-47
View File
@@ -1,47 +0,0 @@
import { v4 as uuidv4 } from 'uuid';
import { JsonManager } from './json_manager';
export class MemoryManager extends JsonManager {
constructor(filePath: string) {
super(filePath); // Call the parent constructor to ensure file initialization
}
// Generate a new unique GUID and ensure it doesn't already exist in the file
private generateUniqueGuid(): Promise<string> {
const generate = async (): Promise<string> => {
const guid = uuidv4();
const value = await this.readValue(guid);
if (value === null) {
return guid;
}
return generate();
};
return generate();
}
// Store meta information with a unique GUID as the key
public async storeMetaInformation(metaInfo: any): Promise<string> {
const guid = await this.generateUniqueGuid();
const success = await this.writeValue(guid, metaInfo);
if (success) {
return guid; // Return the unique GUID for future reference
} else {
throw new Error('Failed to store meta information.');
}
}
// Retrieve meta information using the GUID
public retrieveMetaInformation(guid: string): Promise<any | null> {
return this.readValue(guid);
}
// Update meta information by merging new data into existing data
public async updateMetaInformation(guid: string, newMetaInfo: any): Promise<boolean> {
return await this.writeValue(guid, newMetaInfo);
}
// Remove meta information using the GUID
public removeMetaInformation(guid: string): Promise<boolean> {
return this.removeValue(guid);
}
}
+132 -101
View File
@@ -1,166 +1,197 @@
import {JsonManager} from "./json_manager"; import { UdpClient } from '../network/udp/udp_client'
import {UdpClient} from "../network/udp/udp_client"; import { TcpCommunicator } from './tcp_communicator'
import {TcpCommunicator} from "./tcp_communicator"; import { operationCodes } from '../network/operation_codes'
import {operationCodes} from "../network/operation_codes"; import { ParsedMessage } from '../network/message_handler'
import {ParsedMessage} from "../network/message_handler"; import { JsonDatabase } from '../database/database'
import { DatabaseScheme } from '../database/schemes/database_scheme'
export class NetworkScanner { export class NetworkScanner {
private applicationInfo: JsonManager; private db: JsonDatabase<DatabaseScheme, any>
private userConfig: JsonManager; private readonly udpPort: number
private readonly udpPort: number; private readonly tcpPort: number
private readonly tcpPort: number; private readonly okPage: string
private readonly okPage: string; private readonly errorPage: string
private readonly errorPage: string; private readonly databaseResetPage: string
private readonly databaseResetPage: string; private intervalIds: NodeJS.Timeout[] = []
private appStarted = false;
private intervalIds: NodeJS.Timeout[] = [];
// Flags to prevent overlapping executions // Flags to prevent overlapping executions
private ucCheckBusy = false; private ucCheckBusy = false
private ipLookupBusy = false; private ipLookupBusy = false
private sendLoginBusy = false; private sendLoginBusy = false
constructor(applicationInfoPath: string, userConfigPath: string, udpPort: number, tcpPort: number, okPage: string, errorPage: string, databaseResetPage: string) { constructor(
this.applicationInfo = new JsonManager(applicationInfoPath); pathToDatabaseFile: string,
this.userConfig = new JsonManager(userConfigPath); udpPort: number,
this.udpPort = udpPort; tcpPort: number,
this.tcpPort = tcpPort; okPage: string,
this.okPage = okPage; errorPage: string,
this.errorPage = errorPage; databaseResetPage: string,
this.databaseResetPage = databaseResetPage; ) {
this.db = new JsonDatabase<DatabaseScheme, any>(pathToDatabaseFile)
this.udpPort = udpPort
this.tcpPort = tcpPort
this.okPage = okPage
this.errorPage = errorPage
this.databaseResetPage = databaseResetPage
// Start tasks // Start tasks
this.startUCCheck(); this.startUCCheck()
this.startUserIPLookup(); this.startUserIPLookup()
this.sendLoginRequest(); this.sendAccountCheckRequest()
} }
// Log helper function for consistent logging format // Log helper function for consistent logging format
private log(message: string, level: 'log' | 'error' = 'log', methodName: string = ''): void { private log(message: string, level: 'log' | 'error' = 'log', methodName: string = ''): void {
const prefix = `[NetworkScanner${methodName ? `.${methodName}` : ''}]`; const prefix = `[NetworkScanner${methodName ? `.${methodName}` : ''}]`
if (level === 'error') { if (level === 'error') {
console.error(`${prefix} ${message}`); console.error(`${prefix} ${message}`)
} else { } else {
console.log(`${prefix} ${message}`); console.log(`${prefix} ${message}`)
} }
} }
// UC Check Task // UC Check Task
private startUCCheck(interval: number = 5000): void { private startUCCheck(interval: number = 5000): void {
const intervalId = setInterval(async () => { const intervalId = setInterval(async () => {
if (this.ucCheckBusy) return; if (this.ucCheckBusy) return
this.ucCheckBusy = true; this.ucCheckBusy = true
try { try {
this.log('UC Check running...', 'log', 'startUCCheck'); this.log('UC Check running...', 'log', 'startUCCheck')
const udpClient = new UdpClient(this.udpPort); const udpClient = new UdpClient(this.udpPort)
const aliveClients = await udpClient.getTargetClients(operationCodes.ARE_YOU_UC); const aliveClients = await udpClient.getTargetClients(operationCodes.ARE_YOU_UC)
const storedIp = await this.applicationInfo.readValue('serverIp'); const data = await this.db.read()
const foundClient = aliveClients.length > 0; const foundClient = aliveClients.length > 0
const serverFound = data.app_config.server_found
if (foundClient) { if (foundClient) {
const ipAddress = aliveClients[0]; // Use the first alive client const ipAddress = aliveClients[0]
if (!storedIp || storedIp !== ipAddress) { if (data.network.serverIp !== ipAddress) {
await this.applicationInfo.writeValue('serverIp', ipAddress); await this.db.update((data) => {
if (!this.appStarted) { data.network.serverIp = ipAddress
process.send?.({ type: 'changeContent', page: this.okPage }); return data
})
process.send?.({ type: 'changeContent', page: this.okPage })
} }
this.appStarted = true;
} else if (!this.appStarted) { if (!serverFound) {
process.send?.({ type: 'changeContent', page: this.okPage }); process.send?.({ type: 'changeContent', page: this.okPage })
this.appStarted = true; await this.db.update((data) => {
data.app_config.server_found = true
return data
})
} }
} else { } else {
process.send?.({ type: 'changeContent', page: this.errorPage }); process.send?.({ type: 'changeContent', page: this.errorPage })
await this.db.update((data) => {
data.app_config.server_found = false
return data
})
} }
} catch (err) { } catch (err) {
this.log(`Error checking UC: ${err}`, 'error', 'startUCCheck'); this.log(`Error checking UC: ${err}`, 'error', 'startUCCheck')
process.send?.({ type: 'changeContent', page: this.errorPage }); process.send?.({ type: 'changeContent', page: this.errorPage })
} finally { } finally {
this.ucCheckBusy = false; this.ucCheckBusy = false
} }
}, interval); }, interval)
this.intervalIds.push(intervalId); this.intervalIds.push(intervalId)
} }
// IP Lookup Task // IP Lookup Task
private startUserIPLookup(interval: number = 10000): void { private startUserIPLookup(interval: number = 10000): void {
const intervalId = setInterval(async () => { const intervalId = setInterval(async () => {
if (this.ipLookupBusy) return; if (this.ipLookupBusy) return
this.ipLookupBusy = true; this.ipLookupBusy = true
try { try {
this.log('IP Lookup running...', 'log', 'startUserIPLookup'); this.log('IP Lookup running...', 'log', 'startUserIPLookup')
const serverIp = await this.applicationInfo.readValue('serverIp'); const data = await this.db.read()
const udpClient = new UdpClient(this.udpPort); const serverIp = data.network.serverIp
const activeIPs = await udpClient.getTargetClients(operationCodes.ARE_YOU_HUMAN); const udpClient = new UdpClient(this.udpPort)
const filteredIPs = activeIPs.filter(ip => ip !== serverIp); const activeIPs = await udpClient.getTargetClients(operationCodes.ARE_YOU_HUMAN)
const filteredIPs = activeIPs.filter((ip) => ip !== serverIp)
// Save the filtered IPs to 'users_ip' // Save the filtered IPs to 'users_ip'
await this.applicationInfo.writeValue('users_ip', filteredIPs); await this.db.update((data) => {
data.network.usersInLan = filteredIPs.map((ip) => ({
id: '',
ip,
name: '',
departmentId: '',
}))
return data
})
} catch (err) { } catch (err) {
this.log(`Error during user IP lookup: ${err}`, 'error', 'startUserIPLookup'); this.log(`Error during user IP lookup: ${err}`, 'error', 'startUserIPLookup')
} finally { } finally {
this.ipLookupBusy = false; this.ipLookupBusy = false
} }
}, interval); }, interval)
this.intervalIds.push(intervalId); this.intervalIds.push(intervalId)
} }
// Login Request Task // Login Request Task
private sendLoginRequest(interval: number = 5000): void { private sendAccountCheckRequest(interval: number = 5000): void {
const intervalId = setInterval(async () => { const intervalId = setInterval(async () => {
if (this.sendLoginBusy || this.appStarted) return; if (this.sendLoginBusy) return
this.sendLoginBusy = true; this.sendLoginBusy = true
try { try {
const userInfo = await this.userConfig.readValue('user_info'); const data = await this.db.read()
if (!userInfo || !userInfo.email || !userInfo.password) { const loggedIn = data.app_config.logged_in
this.log("Email or password not found in user config.", 'error', 'sendLoginRequest'); if (!loggedIn) {
return; this.log('User is not logged in.', 'log', 'sendAccountCheckRequest')
return
} }
const app_type = await this.userConfig.readValue('app_type'); const userInfo = data.app_config.user_info
const email = userInfo.email; if (!userInfo || !userInfo.email) {
const password = userInfo.password; this.log(
const serverIp = await this.applicationInfo.readValue('serverIp'); 'Email or password not found in user config.',
'error',
'sendAccountCheckRequest',
)
return
}
const email = userInfo.email
const serverIp = data.network.serverIp
if (!serverIp) { if (!serverIp) {
this.log("Server IP not found in application info.", 'error', 'sendLoginRequest'); this.log('Server IP not found in application info.', 'error', 'sendAccountCheckRequest')
return; return
} }
const tcpCommunicator = new TcpCommunicator(serverIp, this.tcpPort); const tcpCommunicator = new TcpCommunicator(serverIp, this.tcpPort)
if (!await tcpCommunicator.connect()) { if (!(await tcpCommunicator.connect())) {
this.log("Failed to connect to the server.", 'error', 'sendLoginRequest'); this.log('Failed to connect to the server.', 'error', 'sendAccountCheckRequest')
return; return
} }
const metaInfo = { email, password, app_type }; const metaInfo = { email }
if (!await tcpCommunicator.sendMessage(operationCodes.LOGIN, metaInfo)) { if (!(await tcpCommunicator.sendMessage(operationCodes.EMAIL_VERIFICATION, metaInfo))) {
this.log("Failed to send login request.", 'error', 'sendLoginRequest'); this.log('Failed to send login request.', 'error', 'sendAccountCheckRequest')
await tcpCommunicator.disconnect(); await tcpCommunicator.disconnect()
return; return
} }
const response = await this.waitForResponse(tcpCommunicator); const response = await this.waitForResponse(tcpCommunicator)
if (response?.operationCode !== operationCodes.OK) { if (response?.operationCode !== operationCodes.OK) {
await this.userConfig.resetFile(); process.send?.({ type: 'changeContent', page: this.databaseResetPage })
await this.userConfig.writeValue('app_type', app_type);
process.send?.({ type: 'changeContent', page: this.databaseResetPage });
} }
} catch (err) { } catch (err) {
this.log(`Error during login request: ${err}`, 'error', 'sendLoginRequest'); this.log(`Error during login request: ${err}`, 'error', 'sendAccountCheckRequest')
} finally { } finally {
this.sendLoginBusy = false; this.sendLoginBusy = false
} }
}, interval); }, interval)
this.intervalIds.push(intervalId); this.intervalIds.push(intervalId)
} }
// Helper function to wait for a response from the TCP communicator // Helper function to wait for a response from the TCP communicator
@@ -168,18 +199,18 @@ export class NetworkScanner {
return new Promise((resolve) => { return new Promise((resolve) => {
const checkInterval = setInterval(() => { const checkInterval = setInterval(() => {
if (tcpCommunicator.hasResponseArrived()) { if (tcpCommunicator.hasResponseArrived()) {
clearInterval(checkInterval); clearInterval(checkInterval)
resolve(tcpCommunicator.getLastResult()); resolve(tcpCommunicator.getLastResult())
} }
}, 100); }, 100)
}); })
} }
// Method to stop all intervals (for cleanup if needed) // Method to stop all intervals (for cleanup if needed)
public stopAllIntervals(): void { public stopAllIntervals(): void {
for (const id of this.intervalIds) { for (const id of this.intervalIds) {
clearInterval(id); clearInterval(id)
} }
this.log("All intervals have been stopped.", 'log', 'stopAllIntervals'); this.log('All intervals have been stopped.', 'log', 'stopAllIntervals')
} }
} }
-137
View File
@@ -1,137 +0,0 @@
import fs from 'fs';
import path from 'path';
export class QueueManager<T> {
private readonly filePath: string;
private readonly lockFilePath: string;
private queue: T[];
private readonly compareFn: (a: T, b: T) => boolean; // Comparison function
constructor(filePath: string, compareFn: (a: T, b: T) => boolean) {
this.filePath = filePath;
this.lockFilePath = `${filePath}.lock`; // Define the lock file path
this.queue = [];
this.compareFn = compareFn;
const dir = path.dirname(filePath);
// Check if the directory exists, throw error if it doesn't
if (!fs.existsSync(dir)) {
throw new Error(`The directory does not exist: ${dir}`);
}
// If the file doesn't exist, create it
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, JSON.stringify([], null, 2), 'utf8');
}
}
// Method to acquire a lock (create .lock file)
private acquireLock(): void {
while (fs.existsSync(this.lockFilePath)) {
// Wait until the lock file is released
this.sleepSync(100); // 100ms delay before retrying
}
// Create the lock file
fs.writeFileSync(this.lockFilePath, '');
}
// Sleep function to simulate delay for locking mechanism
private sleepSync(ms: number): void {
const start = Date.now();
while (Date.now() - start < ms) {
// busy wait
}
}
// Method to release the lock (delete .lock file)
private releaseLock(): void {
if (fs.existsSync(this.lockFilePath)) {
fs.unlinkSync(this.lockFilePath);
}
}
// Load the queue from the JSON file
loadQueue(): void {
this.acquireLock(); // Acquire the lock
try {
const fileData = fs.readFileSync(this.filePath, 'utf8');
this.queue = JSON.parse(fileData) || [];
} catch (err) {
// If the file doesn't exist or is invalid, start with an empty queue
this.queue = [];
} finally {
this.releaseLock(); // Release the lock
}
}
// Save the queue back to the JSON file
saveQueue(): void {
this.acquireLock(); // Acquire the lock
try {
fs.writeFileSync(this.filePath, JSON.stringify(this.queue, null, 2), 'utf8');
} finally {
this.releaseLock(); // Release the lock
}
}
// Enqueue: Add an item to the end of the queue if it doesn't already exist
enqueue(item: T): void {
this.loadQueue(); // Ensure we load the latest queue
// Check if the item already exists in the queue
const exists = this.queue.some(existingItem => this.compareFn(existingItem, item));
console.log(this.queue);
if (!exists) {
this.queue.push(item);
this.saveQueue(); // Save the updated queue
} else {
console.log('Item already exists in the queue. Skipping enqueue.');
}
}
// Dequeue: Remove an item from the front of the queue
dequeue(): T | null {
this.loadQueue(); // Ensure we load the latest queue
if (this.queue.length === 0) {
return null; // Queue is empty
}
const item = this.queue.shift() as T; // Remove the first item
this.saveQueue(); // Save the updated queue
return item;
}
// Peek: Get the item at the front of the queue without removing it
peek(): T | null {
this.loadQueue(); // Ensure we load the latest queue
return this.queue.length > 0 ? this.queue[0] : null;
}
// Check if the queue is empty
isEmpty(): boolean {
this.loadQueue(); // Ensure we load the latest queue
return this.queue.length === 0;
}
// Get the length of the queue
length(): number {
this.loadQueue(); // Ensure we load the latest queue
return this.queue.length;
}
// Clear the entire queue
clearQueue(): void {
this.acquireLock(); // Acquire the lock
try {
this.queue = []; // Clear the queue
fs.writeFileSync(this.filePath, JSON.stringify(this.queue, null, 2), 'utf8');
} finally {
this.releaseLock(); // Release the lock
}
}
}
+40 -36
View File
@@ -1,86 +1,90 @@
import { TcpClient } from "../network/tcp/tcp_client"; import { TcpClient } from '../network/tcp/tcp_client'
import { ParsedMessage } from "../network/message_handler"; import { ParsedMessage } from '../network/message_handler'
export class TcpCommunicator { export class TcpCommunicator {
private readonly ip: string; private readonly ip: string
private readonly port: number; private readonly port: number
private tcpClient: TcpClient | null = null; private tcpClient: TcpClient | null = null
private lastResult: ParsedMessage | null = null; private lastResult: ParsedMessage | null = null
constructor(ip: string, port: number) { constructor(ip: string, port: number) {
this.ip = ip; this.ip = ip
this.port = port; this.port = port
} }
async connect(): Promise<boolean> { async connect(): Promise<boolean> {
this.tcpClient = new TcpClient(this.port); this.tcpClient = new TcpClient(this.port)
this.tcpClient.openSocket(this.ip); this.tcpClient.openSocket(this.ip)
return this.tcpClient.isSocketConnected(); return this.tcpClient.isSocketConnected()
} }
async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise<boolean> { async sendMessage(
if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return false; operationCode: string,
metaInfo?: { [key: string]: any },
fileContent?: Buffer,
): Promise<boolean> {
if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return false
// Wait until the AES key is set before sending the message // Wait until the AES key is set before sending the message
return new Promise((resolve) => { return new Promise((resolve) => {
const idWaitForAes = setInterval(async () => { const idWaitForAes = setInterval(async () => {
if (this.tcpClient?.isAesKeySet()) { if (this.tcpClient?.isAesKeySet()) {
clearInterval(idWaitForAes); clearInterval(idWaitForAes)
// Send the message once AES key is set // Send the message once AES key is set
const status = await this.tcpClient!.sendMessage(operationCode, metaInfo, fileContent); const status = await this.tcpClient!.sendMessage(operationCode, metaInfo, fileContent)
if (status) { if (status) {
await this.waitForResponse(); await this.waitForResponse()
} }
resolve(status); resolve(status)
} }
}, 100); }, 100)
}); })
} }
getLastResult(): ParsedMessage | null { getLastResult(): ParsedMessage | null {
const message = this.lastResult; const message = this.lastResult
this.lastResult = null; this.lastResult = null
if (global.gc) { if (global.gc) {
global.gc(); global.gc()
} }
return message; return message
} }
hasResponseArrived(): boolean { hasResponseArrived(): boolean {
if(!this.tcpClient) return false; if (!this.tcpClient) return false
return this.lastResult !== null; return this.lastResult !== null
} }
private waitForResponse(): Promise<void> { private waitForResponse(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.tcpClient) { if (!this.tcpClient) {
reject(); reject()
} }
// Start interval for waiting for the response // Start interval for waiting for the response
const responseInterval = setInterval(() => { const responseInterval = setInterval(() => {
if (!this.tcpClient?.isSocketConnected()) { if (!this.tcpClient?.isSocketConnected()) {
clearInterval(responseInterval); clearInterval(responseInterval)
resolve(); resolve()
} }
if (this.tcpClient?.isMessageReceived()) { if (this.tcpClient?.isMessageReceived()) {
this.lastResult = this.tcpClient.getLastResult(); this.lastResult = this.tcpClient.getLastResult()
clearInterval(responseInterval); // Stop checking once we have a response clearInterval(responseInterval) // Stop checking once we have a response
resolve(); resolve()
} }
}, 100); // Check every 100 milliseconds }, 100) // Check every 100 milliseconds
}); })
} }
async disconnect(): Promise<boolean> { async disconnect(): Promise<boolean> {
if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return true; if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return true
this.tcpClient.closeSocket(); this.tcpClient.closeSocket()
return true; return true
} }
} }
+57 -86
View File
@@ -1,122 +1,93 @@
import { JsonManager } from "./json_manager"; import { JsonDatabase } from '../database/database'
import { MemoryManager } from "./memory_manager"; import { DatabaseScheme } from '../database/schemes/database_scheme'
import { operationCodes } from "../network/operation_codes"; import { NetworkUserScheme } from '../database/schemes/network_scheme'
import { TcpCommunicator } from "./tcp_communicator"; import { TcpCommunicator } from './tcp_communicator'
import { ParsedMessage } from "../network/message_handler"; import { operationCodes } from '../network/operation_codes'
import { ParsedMessage } from '../network/message_handler'
export class UsersInfoFetcher { export class UsersInfoFetcher {
private applicationInfo: JsonManager; private db: JsonDatabase<DatabaseScheme, any>
private memoryManager: MemoryManager; private tcpCommunicator: TcpCommunicator | null = null
private tcpCommunicator: TcpCommunicator | null = null; private readonly clientPort: number
private readonly clientPort: number; private intervalId: NodeJS.Timeout | null = null
private memoryId: string;
private readonly activeUsersKey: string;
private intervalId: NodeJS.Timeout | null = null;
constructor(applicationInfoPath: string, memoryManagerPath: string, clientPort: number) { constructor(pathToDatabaseFile: string, clientPort: number) {
this.applicationInfo = new JsonManager(applicationInfoPath); this.db = new JsonDatabase(pathToDatabaseFile)
this.memoryManager = new MemoryManager(memoryManagerPath); this.clientPort = clientPort
this.memoryId = ''; this.tcpCommunicator = null
this.clientPort = clientPort;
this.tcpCommunicator = null;
this.activeUsersKey = 'active_users_info';
} }
// Method to start checking user info periodically (every minute) // Start fetching user info periodically
async start(): Promise<void> { async start(): Promise<void> {
this.intervalId = setInterval(async () => { this.intervalId = setInterval(async () => {
await this.initialize(); // Re-run every minute const data = await this.db.read()
const usersIps = data.network.usersInLan.map((user: NetworkUserScheme) => user.ip)
if (global.gc) {
global.gc();
}
}, 5000); // 5-second interval for testing
}
// Initialize and fetch user IPs and process users info
private async initialize() {
const usersIps = await this.applicationInfo.readValue('users_ip');
if (!usersIps) {
this.log('No IP addresses found in users_ip', 'error');
return;
}
// Ensure active_users_info exists in the memory
this.memoryId = await this.applicationInfo.readValue(this.activeUsersKey);
if (!this.memoryId) {
this.memoryId = await this.memoryManager.storeMetaInformation([]);
await this.applicationInfo.writeValue(this.activeUsersKey, this.memoryId);
}
// Check user information
await this.checkUsersInfo(usersIps);
}
// Check user info from the list of IPs
private async checkUsersInfo(usersIps: string[]) {
let usersInfo: Array<{ ip: string, user_info: any }> = []; // Array to store IP and user_info objects
for (const ip of usersIps) { for (const ip of usersIps) {
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort); this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort)
if (!await this.tcpCommunicator.connect()) { if (!(await this.tcpCommunicator.connect())) {
this.log(`Failed to open connection for IP: ${ip}`, 'error'); this.log(`Failed to open connection for IP: ${ip}`, 'error')
continue; continue
} }
if(!await this.tcpCommunicator?.sendMessage(operationCodes.GET_USER_INFORMATION)){ if (!(await this.tcpCommunicator?.sendMessage(operationCodes.GET_USER_INFORMATION))) {
await this.tcpCommunicator.disconnect(); await this.tcpCommunicator.disconnect()
continue; continue
} }
// Wait for the response for 10 seconds // Wait for the response
const response = await this.waitForResponse(); const response = await this.waitForResponse()
// If a response is received and is successful, append it to usersInfo
if (response && response.metaInfo) { if (response && response.metaInfo) {
usersInfo.push({ await this.db.update((dbData) => {
ip: ip, const userIndex = dbData.network.usersInLan.findIndex((user) => user.ip === ip)
user_info: response.metaInfo
}); if (userIndex !== -1) {
// @ts-ignore
dbData.network.usersInLan[userIndex].id = response.metaInfo.id
// @ts-ignore
dbData.network.usersInLan[userIndex].name = response.metaInfo.name
// @ts-ignore
dbData.network.usersInLan[userIndex].departmentId = response.metaInfo.departmentId
} else {
this.log(`User with IP ${ip} not found in the database.`, 'error')
} }
await this.tcpCommunicator.disconnect(); return dbData
})
} }
await this.updateActiveUsers(usersInfo); // Update active users information in the memory await this.tcpCommunicator.disconnect()
}
if (global.gc) {
global.gc()
}
}, 5000) // 5-second interval for testing
} }
private async waitForResponse(): Promise<ParsedMessage | null> { private async waitForResponse(): Promise<ParsedMessage | null> {
return new Promise((resolve) => { return new Promise((resolve) => {
const idResponseCheck = setInterval(async () => { const idResponseCheck = setInterval(async () => {
if (!this.tcpCommunicator) return null; if (!this.tcpCommunicator) return null
if (this.tcpCommunicator.hasResponseArrived()) { if (this.tcpCommunicator.hasResponseArrived()) {
clearInterval(idResponseCheck); clearInterval(idResponseCheck)
resolve(this.tcpCommunicator.getLastResult()); // Resolve the response or null if not available resolve(this.tcpCommunicator.getLastResult())
} }
}, 100); // Check every 100 milliseconds if the response has arrived }, 100) // Check every 100ms
}); })
}
// Update active users information in the memory
private async updateActiveUsers(userInfo: any[]) {
await this.memoryManager.updateMetaInformation(this.memoryId, userInfo); // Update active users in memory
} }
stop(): void { stop(): void {
if (this.intervalId) { if (this.intervalId) {
clearInterval(this.intervalId); clearInterval(this.intervalId)
this.intervalId = null; this.intervalId = null
console.log("[UsersInfoFetcher] Stopped successfully."); this.log('Stopped successfully.')
} }
} }
// Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void { private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[UsersInfoFetcher]'; const prefix = '[UsersInfoFetcher]'
if (level === 'error') { level === 'error' ? console.error(`${prefix} ${message}`) : console.log(`${prefix} ${message}`)
console.error(`${prefix} ${message}`);
} else {
console.log(`${prefix} ${message}`);
}
} }
} }
+53 -53
View File
@@ -1,25 +1,25 @@
import { BrowserWindow, dialog, shell } from 'electron'; import { BrowserWindow, dialog, shell } from 'electron'
import fs from 'fs'; import fs from 'fs'
import path from 'path'; import path from 'path'
export class WindowManager { export class WindowManager {
private readonly mainWindow: BrowserWindow; private readonly mainWindow: BrowserWindow
private readonly pathToPagesDir: string; private readonly pathToPagesDir: string
private announcementWindow: BrowserWindow | null = null; private announcementWindow: BrowserWindow | null = null
constructor(mainWindow: BrowserWindow, pathToPagesDir: string) { constructor(mainWindow: BrowserWindow, pathToPagesDir: string) {
this.pathToPagesDir = pathToPagesDir; this.pathToPagesDir = pathToPagesDir
this.mainWindow = mainWindow; this.mainWindow = mainWindow
this.log('WindowManager initialized.'); this.log('WindowManager initialized.')
} }
// Logging helper function // Logging helper function
private log(message: string, level: 'log' | 'error' = 'log'): void { private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[WindowManager]'; const prefix = '[WindowManager]'
if (level === 'error') { if (level === 'error') {
console.error(`${prefix} ${message}`); console.error(`${prefix} ${message}`)
} else { } else {
console.log(`${prefix} ${message}`); console.log(`${prefix} ${message}`)
} }
} }
@@ -31,10 +31,10 @@ export class WindowManager {
title: 'Alert', title: 'Alert',
message: message, message: message,
buttons: ['OK'], buttons: ['OK'],
}); })
this.log(`Alert displayed with message: "${message}"`); this.log(`Alert displayed with message: "${message}"`)
} else { } else {
this.log('Main window is not available.', 'error'); this.log('Main window is not available.', 'error')
} }
} }
@@ -42,18 +42,18 @@ export class WindowManager {
async changeContent(destination: string): Promise<void> { async changeContent(destination: string): Promise<void> {
if (this.mainWindow) { if (this.mainWindow) {
try { try {
const destinationPath = path.join(this.pathToPagesDir, `${destination}.html`); const destinationPath = path.join(this.pathToPagesDir, `${destination}.html`)
this.log(`Navigating to: ${destinationPath}`); this.log(`Navigating to: ${destinationPath}`)
// Load the destination HTML file into the main window // Load the destination HTML file into the main window
await this.mainWindow.loadFile(destinationPath); await this.mainWindow.loadFile(destinationPath)
this.log(`Navigated to ${destination}`); this.log(`Navigated to ${destination}`)
} catch (error) { } catch (error) {
this.log(`Error changing content: ${error}`, 'error'); this.log(`Error changing content: ${error}`, 'error')
throw error; // Pass the error back to the render process throw error // Pass the error back to the render process
} }
} else { } else {
this.log('Main window is not available.', 'error'); this.log('Main window is not available.', 'error')
} }
} }
@@ -61,14 +61,14 @@ export class WindowManager {
async selectDirectory(): Promise<string | undefined> { async selectDirectory(): Promise<string | undefined> {
const result = await dialog.showOpenDialog(this.mainWindow, { const result = await dialog.showOpenDialog(this.mainWindow, {
properties: ['openDirectory'], // Only allow selecting directories properties: ['openDirectory'], // Only allow selecting directories
}); })
if (result.filePaths && result.filePaths.length > 0) { if (result.filePaths && result.filePaths.length > 0) {
this.log(`Directory selected: ${result.filePaths[0]}`); this.log(`Directory selected: ${result.filePaths[0]}`)
return result.filePaths[0]; // Return the selected directory path return result.filePaths[0] // Return the selected directory path
} else { } else {
this.log('No directory selected.'); this.log('No directory selected.')
return undefined; // Return undefined if no directory was selected return undefined // Return undefined if no directory was selected
} }
} }
@@ -76,13 +76,13 @@ export class WindowManager {
async showFileInExplorer(filePath: string): Promise<void> { async showFileInExplorer(filePath: string): Promise<void> {
if (filePath && fs.existsSync(filePath)) { if (filePath && fs.existsSync(filePath)) {
try { try {
shell.showItemInFolder(filePath); shell.showItemInFolder(filePath)
this.log(`Opened file explorer for: ${filePath}`); this.log(`Opened file explorer for: ${filePath}`)
} catch (error: any) { } catch (error: any) {
this.log(`Error showing file in explorer: ${error.message}`, 'error'); this.log(`Error showing file in explorer: ${error.message}`, 'error')
} }
} else { } else {
this.log('File path is undefined or does not exist.', 'error'); this.log('File path is undefined or does not exist.', 'error')
} }
} }
@@ -91,29 +91,29 @@ export class WindowManager {
const result = await dialog.showOpenDialog(this.mainWindow, { const result = await dialog.showOpenDialog(this.mainWindow, {
properties: ['openFile'], // Allow selecting a file properties: ['openFile'], // Allow selecting a file
filters: [ filters: [
{ name: 'All Files', extensions: ['*'] } // Optionally filter for specific file types { name: 'All Files', extensions: ['*'] }, // Optionally filter for specific file types
] ],
}); })
if (result.filePaths && result.filePaths.length > 0) { if (result.filePaths && result.filePaths.length > 0) {
this.log(`File selected: ${result.filePaths[0]}`); this.log(`File selected: ${result.filePaths[0]}`)
return result.filePaths[0]; // Return the selected file path return result.filePaths[0] // Return the selected file path
} else { } else {
this.log('No file selected.'); this.log('No file selected.')
return undefined; // Return undefined if no file was selected return undefined // Return undefined if no file was selected
} }
} }
// Method to display an announcement in a new window // Method to display an announcement in a new window
async displayAnnouncement(): Promise<void> { async displayAnnouncement(): Promise<void> {
if (this.announcementWindow) { if (this.announcementWindow) {
this.announcementWindow.focus(); this.announcementWindow.focus()
this.log('Announcement window focused.'); this.log('Announcement window focused.')
return; return
} }
const mainScreen = require('electron').screen.getPrimaryDisplay(); const mainScreen = require('electron').screen.getPrimaryDisplay()
const { width, height } = mainScreen.size; const { width, height } = mainScreen.size
this.announcementWindow = new BrowserWindow({ this.announcementWindow = new BrowserWindow({
width: width / 3, width: width / 3,
@@ -125,25 +125,25 @@ export class WindowManager {
contextIsolation: true, contextIsolation: true,
nodeIntegration: false, nodeIntegration: false,
}, },
}); })
this.announcementWindow.removeMenu(); this.announcementWindow.removeMenu()
const announcementPath = path.join(this.pathToPagesDir, 'announcement.html'); const announcementPath = path.join(this.pathToPagesDir, 'announcement.html')
await this.announcementWindow.loadFile(announcementPath); await this.announcementWindow.loadFile(announcementPath)
this.log(`Announcement window opened at: ${announcementPath}`); this.log(`Announcement window opened at: ${announcementPath}`)
// Handle window close // Handle window close
this.announcementWindow.on('closed', () => { this.announcementWindow.on('closed', () => {
this.announcementWindow = null; this.announcementWindow = null
this.log('Announcement window closed.'); this.log('Announcement window closed.')
}); })
} }
async closeAnnouncementWindow(): Promise<void> { async closeAnnouncementWindow(): Promise<void> {
if (this.announcementWindow) { if (this.announcementWindow) {
this.announcementWindow.close(); this.announcementWindow.close()
this.log('Announcement window closed by user.'); this.log('Announcement window closed by user.')
} }
} }
} }
+71 -61
View File
@@ -1,121 +1,131 @@
import { fork, ChildProcess } from 'child_process'; import { fork, ChildProcess } from 'child_process'
import path from 'path'; import path from 'path'
import { WindowManager } from "./window_manager"; import { WindowManager } from './window_manager'
export class WorkerManager { export class WorkerManager {
private readonly pathToWorkerDir: string; private readonly pathToWorkerDir: string
private windowManager: WindowManager; private windowManager: WindowManager
private workers: ChildProcess[]; private workers: ChildProcess[]
private cleanupInProgress: boolean = false; private cleanupInProgress: boolean = false
constructor(pathToWorkerDir: string, windowManager: WindowManager) { constructor(pathToWorkerDir: string, windowManager: WindowManager) {
this.pathToWorkerDir = pathToWorkerDir; this.pathToWorkerDir = pathToWorkerDir
this.windowManager = windowManager; this.windowManager = windowManager
this.workers = []; // Initialize the array to store child processes this.workers = [] // Initialize the array to store child processes
} }
async startNetworkScannerWorker(udpPort: number, tcpPort: number, okPage: string, errorPage: string, databaseResetPage: string, userConfigPath: string, applicationInfoPath: string): Promise<void> { async startNetworkScannerWorker(
udpPort: number,
tcpPort: number,
okPage: string,
errorPage: string,
databaseResetPage: string,
pathToDatabaseFile: string,
): Promise<void> {
return this.startForkedWorker('network_scanner_worker.js', { return this.startForkedWorker('network_scanner_worker.js', {
UDP_PORT: udpPort.toString(), UDP_PORT: udpPort.toString(),
TCP_PORT: tcpPort.toString(), TCP_PORT: tcpPort.toString(),
OK_PAGE: okPage, OK_PAGE: okPage,
ERROR_PAGE: errorPage, ERROR_PAGE: errorPage,
DATABASE_RESET_PAGE: databaseResetPage, DATABASE_RESET_PAGE: databaseResetPage,
USER_CONFIG_PATH: userConfigPath, DATABASE_FILE_PATH: pathToDatabaseFile,
APPLICATION_INFO_PATH: applicationInfoPath })
});
} }
async startDirectoriesWatchersWorker(memoryManagerPath: string, applicationInfoPath: string): Promise<void> { async startDirectoriesWatchersWorker(pathToDatabaseFile: string): Promise<void> {
return this.startForkedWorker('directories_watcher_worker.js', { return this.startForkedWorker('directories_watcher_worker.js', {
MEMORY_MANAGER_PATH: memoryManagerPath, DATABASE_FILE_PATH: pathToDatabaseFile,
APPLICATION_INFO_PATH: applicationInfoPath })
});
} }
async startServersWorker(host: string, udpPort: number, tcpPort: number): Promise<void> { async startServersWorker(host: string, udpPort: number, tcpPort: number): Promise<void> {
return this.startForkedWorker('servers_worker.js', { return this.startForkedWorker('servers_worker.js', {
HOST: host, HOST: host,
USER_UDP_PORT: udpPort.toString(), USER_UDP_PORT: udpPort.toString(),
USER_TCP_PORT: tcpPort.toString() USER_TCP_PORT: tcpPort.toString(),
}); })
} }
async startResourceCoordinatorWorker(usersConfigPath: string, applicationInfoPath: string, memoryManagerPath: string, queueManagerPath: string, tcpPort: number): Promise<void> { async startResourceCoordinatorWorker(
pathToDatabaseFiles: string,
tcpPort: number,
): Promise<void> {
return this.startForkedWorker('resource_coordinator_worker.js', { return this.startForkedWorker('resource_coordinator_worker.js', {
USERS_CONFIG_PATH: usersConfigPath, DATABASE_FILE_PATH: pathToDatabaseFiles,
APPLICATION_INFO_PATH: applicationInfoPath, TCP_PORT: tcpPort.toString(),
MEMORY_MANAGER_PATH: memoryManagerPath, })
QUEUE_MANAGER_PATH: queueManagerPath,
TCP_PORT: tcpPort.toString()
});
} }
async startBackupRetrievalWorker(userConfigPath: string, applicationInfoPath: string, clientPort: number, destinationPath: string): Promise<void> { async startBackupRetrievalWorker(
clientPort: number,
destinationPath: string,
pathToDatabaseFile: string,
): Promise<void> {
return this.startForkedWorker('backup_retrieval_worker.js', { return this.startForkedWorker('backup_retrieval_worker.js', {
USER_CONFIG_PATH: userConfigPath,
APPLICATION_INFO_PATH: applicationInfoPath,
CLIENT_PORT: clientPort.toString(), CLIENT_PORT: clientPort.toString(),
DESTINATION_PATH: destinationPath DESTINATION_PATH: destinationPath,
}); DATABASE_FILE_PATH: pathToDatabaseFile,
})
} }
private async startForkedWorker(scriptName: string, envData: { [key: string]: string }): Promise<void> { private async startForkedWorker(
scriptName: string,
envData: { [key: string]: string },
): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const worker = fork(path.join(this.pathToWorkerDir, scriptName), { const worker = fork(path.join(this.pathToWorkerDir, scriptName), {
execArgv: ['--max-old-space-size=4096'], // Set memory limit for the forked process execArgv: ['--max-old-space-size=4096'], // Set memory limit for the forked process
env: { ...process.env, ...envData } // Merge environment variables env: { ...process.env, ...envData }, // Merge environment variables
}); })
this.workers.push(worker); // Store the worker reference this.workers.push(worker) // Store the worker reference
worker.on('message', (data: unknown) => { worker.on('message', (data: unknown) => {
const message = data as { type: string, page?: string, message?: string }; // Type casting for message const message = data as { type: string; page?: string; message?: string } // Type casting for message
if (message.type === 'changeContent' && message.page) { if (message.type === 'changeContent' && message.page) {
this.windowManager.changeContent(message.page); this.windowManager.changeContent(message.page)
} else if (message.type === 'showAlert' && message.message) { } else if (message.type === 'showAlert' && message.message) {
this.windowManager.showAlert(message.message); this.windowManager.showAlert(message.message)
} else { } else {
console.log(`${scriptName} message:`, message); console.log(`${scriptName} message:`, message)
} }
}); })
worker.on('error', (err) => { worker.on('error', (err) => {
console.error(`${scriptName} error:`, err); console.error(`${scriptName} error:`, err)
worker.kill(); worker.kill()
this.removeWorker(worker); this.removeWorker(worker)
reject(err); reject(err)
}); })
worker.on('exit', (code, signal) => { worker.on('exit', (code, signal) => {
this.removeWorker(worker); this.removeWorker(worker)
if (code === 0) { if (code === 0) {
console.log(`${scriptName} exited successfully`); console.log(`${scriptName} exited successfully`)
resolve(); resolve()
} else if (signal) { } else if (signal) {
console.log(`${scriptName} was killed with signal: ${signal}`); console.log(`${scriptName} was killed with signal: ${signal}`)
} else { } else {
console.error(`${scriptName} exited with code: ${code}`);; console.error(`${scriptName} exited with code: ${code}`)
} }
}); })
}); })
} }
closeAllWorkers(): void { closeAllWorkers(): void {
if (this.cleanupInProgress) return; // Prevent duplicate cleanup if (this.cleanupInProgress) return // Prevent duplicate cleanup
this.cleanupInProgress = true; this.cleanupInProgress = true
console.log('Terminating all running workers...'); console.log('Terminating all running workers...')
this.workers.forEach(worker => worker.kill()); this.workers.forEach((worker) => worker.kill())
this.workers = []; this.workers = []
} }
// Helper method to remove a worker from the workers array when it exits
private removeWorker(worker: ChildProcess): void { private removeWorker(worker: ChildProcess): void {
const index = this.workers.indexOf(worker); const index = this.workers.indexOf(worker)
if (index > -1) { if (index > -1) {
this.workers.splice(index, 1); this.workers.splice(index, 1)
} }
} }
} }
+4 -4
View File
@@ -1,8 +1,8 @@
export interface FileItemTask { export interface FileItemTask {
ip: string; ip: string
path: string; path: string
userName: string; userName: string
} }
export const compareFnFileItemTask = (task1: FileItemTask, task2: FileItemTask) => export const compareFnFileItemTask = (task1: FileItemTask, task2: FileItemTask) =>
task1.ip === task2.ip && task1.path === task2.path; task1.ip === task2.ip && task1.path === task2.path
+9 -9
View File
@@ -1,15 +1,15 @@
interface PoolRequest { interface PoolRequest {
type: PoolOperation; // Renamed to PoolOperation type: PoolOperation // Renamed to PoolOperation
clientId: string; // Add clientId to the request clientId: string // Add clientId to the request
data: PoolDataBundle; data: PoolDataBundle
} }
interface PoolDataBundle { interface PoolDataBundle {
port?: number; port?: number
ip?: string; ip?: string
operationCode?: string; // Keep operationCode here operationCode?: string // Keep operationCode here
metaInfo?: { [key: string]: any }; metaInfo?: { [key: string]: any }
fileContent?: Buffer; fileContent?: Buffer
} }
type PoolOperation = 'open' | 'send' | 'close'; // Define the allowed PoolOperations type PoolOperation = 'open' | 'send' | 'close' // Define the allowed PoolOperations
+3 -3
View File
@@ -1,5 +1,5 @@
interface RegisteredClient { interface RegisteredClient {
id: string; id: string
ip: string; ip: string
port: number; port: number
} }
+3 -3
View File
@@ -1,6 +1,6 @@
// Define a type for the message structure // Define a type for the message structure
interface WorkerMessage { interface WorkerMessage {
type: 'changeContent' | 'showAlert' | 'log'; type: 'changeContent' | 'showAlert' | 'log'
page?: string; page?: string
message?: string; message?: string
} }
+137
View File
@@ -0,0 +1,137 @@
import { JsonDatabase } from '../database/database'
import { DatabaseScheme } from '../database/schemes/database_scheme'
import path from 'path'
import { EncryptionKeyScheme, UserInfoScheme } from '../database/schemes/app_config_scheme'
import { FileItemTask } from '../database/helpers/queue_manager'
import { DirectoryInfo, DirectorySchemes } from '../database/schemes/local_resources_scheme'
const pathToDatabaseFile = path.join(__dirname, '..', 'database.json')
class IpcDatabaseHandler {
private readonly db: JsonDatabase<DatabaseScheme, any>
constructor(pathToDatabaseFile: string) {
this.db = new JsonDatabase(pathToDatabaseFile)
}
async getAppType(): Promise<string> {
const data = await this.db.read()
return data.app_config.app_type
}
async getUserInfo(): Promise<UserInfoScheme> {
const data = await this.db.read()
return data.app_config.user_info
}
async getActiveUsers(): Promise<any> {
const data = await this.db.read()
return data.network.usersInLan
}
async getLocalResources(): Promise<DirectorySchemes> {
const data = await this.db.read()
return data.local_resources.directory_schemes
}
async getDirectoryInfo(id: string): Promise<DirectoryInfo> {
const data = await this.db.read()
const directorySchemes = data.local_resources.directory_schemes
switch (id) {
case directorySchemes.backup.id: {
return directorySchemes.backup
}
case directorySchemes.department.id: {
return directorySchemes.department
}
case directorySchemes.shared.id: {
return directorySchemes.shared
}
default: {
throw new Error(`Directory with id ${id} not found`)
}
}
}
async writeDirectoryPath(id: string, path: string): Promise<boolean> {
try {
await this.db.update((data) => {
switch (id) {
case data.local_resources.directory_schemes.backup.id: {
data.local_resources.directory_schemes.backup.path = path
break
}
case data.local_resources.directory_schemes.department.id: {
data.local_resources.directory_schemes.department.path = path
break
}
case data.local_resources.directory_schemes.shared.id: {
data.local_resources.directory_schemes.shared.path = path
break
}
default: {
throw new Error(`Directory with id ${id} not found`)
}
}
return data
})
return true
}
catch (e) {
return false
}
}
async isBackupSet(): Promise<boolean> {
const data = await this.db.read()
return data.local_resources.directory_schemes.backup.path !== ''
}
async writeUserInfo(userInfo: UserInfoScheme): Promise<boolean> {
await this.db.update((data) => {
data.app_config.user_info = userInfo
return data
})
return true
}
async writeEncryptionKey(encryptionKey: EncryptionKeyScheme): Promise<boolean> {
await this.db.update((data) => {
data.app_config.encryption_key = encryptionKey
return data
})
return true
}
async setLoginStatus(status: boolean): Promise<boolean> {
await this.db.update((data) => {
data.app_config.logged_in = status
return data
})
return true
}
async addTaskToSendFileQueue(task: FileItemTask) {
this.db.pushQueue(task)
}
async resetInternalDatabase(): Promise<boolean> {
await this.db.reset()
return true
}
}
export const ipcDatabaseHandler = new IpcDatabaseHandler(pathToDatabaseFile)
+60
View File
@@ -0,0 +1,60 @@
import { TcpCommunicator } from '../helpers/tcp_communicator'
import dotenv from 'dotenv'
import path from 'path'
import { JsonDatabase } from '../database/database'
import { DatabaseScheme } from '../database/schemes/database_scheme'
import { operationCodes } from '../network/operation_codes'
dotenv.config({ path: path.join(__dirname, '..', '..', '.env') })
const pathToDatabaseFile = path.join(__dirname, '..', 'database.json')
class IpcUCHandler {
private readonly TCP_PORT: number
private readonly db: JsonDatabase<DatabaseScheme, any>
private tcpCommunicator: TcpCommunicator | null = null
constructor(pathToDatabaseFile: string) {
this.TCP_PORT = process.env.TCP_PORT ? parseInt(process.env.TCP_PORT) : 41234
this.db = new JsonDatabase(pathToDatabaseFile)
}
async openUcSocket(): Promise<boolean> {
if (!this.db) throw new Error('TcpMethods is not initialized.')
const data = await this.db.read()
const serverIp = data.network.serverIp
this.tcpCommunicator = new TcpCommunicator(serverIp, this.TCP_PORT)
return await this.tcpCommunicator.connect()
}
async sendUcMessage(
operationCode: string,
metaInfo?: { [key: string]: any },
fileContent?: Buffer,
) {
if (!this.tcpCommunicator) throw new Error('TcpMethods is not initialized.')
return await this.tcpCommunicator.sendMessage(operationCode, metaInfo, fileContent)
}
async hasResponseArrived() {
if (!this.tcpCommunicator) throw new Error('TcpMethods is not initialized.')
return this.tcpCommunicator.hasResponseArrived()
}
async getLastUcResult() {
if (!this.tcpCommunicator) throw new Error('TcpMethods is not initialized.')
return this.tcpCommunicator.getLastResult()
}
async closeUcSocket() {
if (!this.tcpCommunicator) throw new Error('TcpMethods is not initialized.')
return await this.tcpCommunicator.disconnect()
}
async getOperationsCodes(): Promise<{ [key: string]: string }> {
return operationCodes
}
}
export const ipcUCHandler = new IpcUCHandler(pathToDatabaseFile)
View File
+156 -263
View File
@@ -1,128 +1,114 @@
import {app, BrowserWindow, ipcMain, IpcMainInvokeEvent} from 'electron'; import { app, BrowserWindow, ipcMain, IpcMainInvokeEvent } from 'electron'
import path from 'path'; import path from 'path'
import { promises as fs } from 'fs'; import { promises as fs } from 'fs'
import dotenv from 'dotenv'; import dotenv from 'dotenv'
import { v4 as uuidv4 } from 'uuid'
import {WorkerManager} from "../helpers/worker_manager"; import { WorkerManager } from '../helpers/worker_manager'
import {DirectoryWatcher} from "../helpers/directory_watcher"; import { DirectoryWatcher } from '../helpers/directory_watcher'
import {QueueManager} from "../helpers/queue_manager"; import { WindowManager } from '../helpers/window_manager'
import {compareFnFileItemTask, FileItemTask} from "../interfaces/file_item_task";
import {WindowManager} from "../helpers/window_manager";
import {JsonManager} from "../helpers/json_manager";
import {MemoryManager} from "../helpers/memory_manager";
import {TcpCommunicator} from "../helpers/tcp_communicator";
import {operationCodes} from "../network/operation_codes"; import os from 'os'
import os from "os"; import { JsonDatabase } from '../database/database'
import { DatabaseScheme } from '../database/schemes/database_scheme'
// Load environment variables // Load environment variables
dotenv.config({ path: path.join(__dirname, '..', '..', '.env') }); dotenv.config({ path: path.join(__dirname, '..', '..', '.env') })
const UDP_PORT = process.env.UDP_PORT ? parseInt(process.env.UDP_PORT) : 41233; const UDP_PORT = process.env.UDP_PORT ? parseInt(process.env.UDP_PORT) : 41233
const TCP_PORT = process.env.TCP_PORT ? parseInt(process.env.TCP_PORT) : 41234; const TCP_PORT = process.env.TCP_PORT ? parseInt(process.env.TCP_PORT) : 41234
const HOST = getLocalIp(); const HOST = getLocalIp()
let mainWindow: BrowserWindow | null = null; let mainWindow: BrowserWindow | null = null
let windowManager: WindowManager | null = null; let windowManager: WindowManager | null = null
let tcpCommunicator: TcpCommunicator | null = null; let db: JsonDatabase<DatabaseScheme, any> | null = null
let userConfig: JsonManager | null = null; let workerManager: WorkerManager | null = null
let applicationInfo: JsonManager | null = null; let directoryWatcher: DirectoryWatcher | null = null
let memoryManager: MemoryManager | null = null; let announcementWatcher: NodeJS.Timeout | null = null
let workerManager: WorkerManager | null = null; let resetApplicationWatcher: NodeJS.Timeout | null = null
let backupDirectoryManager: DirectoryWatcher | null = null;
let departmentShareManager: DirectoryWatcher | null = null;
let sendFileQueue: QueueManager<FileItemTask> | null = null;
let announcementWatcher: NodeJS.Timeout | null = null;
let resetApplicationWatcher: NodeJS.Timeout | null = null;
const pathToPagesDir = path.join(__dirname, '..', '..', 'render', 'html'); const pathToPagesDir = path.join(__dirname, '..', '..', 'render', 'html')
const pathToWorkerDir = path.join(__dirname, '..', 'workers'); const pathToWorkerDir = path.join(__dirname, '..', 'workers')
const pathToJsons = path.join(__dirname, '..', 'json_files'); const pathToDatabaseFile = path.join(__dirname, '..', 'database.json')
const pathToClientsBackups = path.join(__dirname, '..', 'backups'); const pathToClientsBackups = path.join(__dirname, '..', 'backups')
async function cleanupAndExit() { async function cleanupAndExit() {
// Stop all workers // Stop all workers
if (workerManager) { if (workerManager) {
console.log('Terminating all workers...'); console.log('Terminating all workers...')
workerManager.closeAllWorkers();
}
// Reset memory
if (memoryManager) {
await memoryManager.resetFile();
}
// Close watchers
if (backupDirectoryManager) {
console.log('Stopping backup directory watcher...');
backupDirectoryManager.closeWatcher(); // Add this method to DirectoryWatcher to close the watcher
}
if (departmentShareManager) {
console.log('Stopping department directory watcher...');
departmentShareManager.closeWatcher(); // Add this method to DirectoryWatcher to close the watcher
}
if(workerManager){
console.log('Terminating all workers...');
workerManager.closeAllWorkers() workerManager.closeAllWorkers()
} }
console.log('Cleanup complete, exiting application.'); // Close watchers
app.quit(); // This will properly close the application if (directoryWatcher) {
console.log('Stopping backup directory watcher...')
directoryWatcher.stopAllWatchers() // Add this method to DirectoryWatcher to close the watcher
}
if (workerManager) {
console.log('Terminating all workers...')
workerManager.closeAllWorkers()
}
console.log('Cleanup complete, exiting application.')
app.quit() // This will properly close the application
} }
async function ensureDirectoryExists(dirPath: string): Promise<void> { async function ensureDirectoryExists(dirPath: string): Promise<void> {
try { try {
await fs.access(dirPath); await fs.access(dirPath)
} catch (err) { } catch (err) {
// If the directory doesn't exist, create it // If the directory doesn't exist, create it
await fs.mkdir(dirPath, { recursive: true }); await fs.mkdir(dirPath, { recursive: true })
console.log(`Directory created: ${dirPath}`); console.log(`Directory created: ${dirPath}`)
} }
} }
function getLocalIp() { function getLocalIp() {
const interfaces = os.networkInterfaces(); const interfaces = os.networkInterfaces()
for (let interfaceName in interfaces) { for (let interfaceName in interfaces) {
const addresses = interfaces[interfaceName]; const addresses = interfaces[interfaceName]
if(!addresses) continue; if (!addresses) continue
for (let address of addresses) { for (let address of addresses) {
// Filter for IPv4 and ignore internal (127.0.0.1) addresses // Filter for IPv4 and ignore internal (127.0.0.1) addresses
if (address.family === 'IPv4' && !address.internal) { if (address.family === 'IPv4' && !address.internal) {
return address.address; return address.address
} }
} }
} }
return ''; // Fallback if no IP is found return '' // Fallback if no IP is found
} }
function startAnnouncementWatcher() { function startAnnouncementWatcher() {
const checkInterval = 5000; // Check every 5 seconds const checkInterval = 5000 // Check every 5 seconds
announcementWatcher = setInterval(async () => { announcementWatcher = setInterval(async () => {
if(!windowManager || !applicationInfo) return; if (!windowManager || !db) return
const announcement = await applicationInfo.readValue('announcement'); const data = await db.read()
if (announcement) await windowManager.displayAnnouncement(); if (data.network.announcement) await windowManager.displayAnnouncement()
}, checkInterval); }, checkInterval)
} }
function startResetApplicationWatcher() { function startResetApplicationWatcher() {
const checkInterval = 5000; // Check every 5 seconds const checkInterval = 5000 // Check every 5 seconds
resetApplicationWatcher = setInterval(async () => { resetApplicationWatcher = setInterval(async () => {
if(!applicationInfo || !windowManager) return; /*
const resetApplicationPreferences = await applicationInfo.readValue('reset_application_preferences'); if (!applicationInfo || !windowManager) return
const resetApplicationPreferences = await applicationInfo.readValue(
'reset_application_preferences',
)
if (resetApplicationPreferences) await windowManager.changeContent('reset-database'); if (resetApplicationPreferences) await windowManager.changeContent('reset-database')
}, checkInterval); */
}, checkInterval)
} }
app.whenReady().then(async () => { app.whenReady().then(async () => {
const title = 'Application'; const title = 'Application'
const mainScreen = require('electron').screen.getPrimaryDisplay(); const mainScreen = require('electron').screen.getPrimaryDisplay()
const { width, height } = mainScreen.size; const { width, height } = mainScreen.size
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
title, title,
@@ -133,219 +119,126 @@ app.whenReady().then(async () => {
preload: path.join(__dirname, 'preload.js'), preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
sandbox: false,
}, },
}); })
mainWindow.removeMenu(); //mainWindow.removeMenu()
await ensureDirectoryExists(pathToJsons); await ensureDirectoryExists(pathToClientsBackups)
await ensureDirectoryExists(pathToClientsBackups);
windowManager = new WindowManager(mainWindow, pathToPagesDir); windowManager = new WindowManager(mainWindow, pathToPagesDir)
userConfig = new JsonManager(path.join(pathToJsons, 'userConfig.json')); workerManager = new WorkerManager(pathToWorkerDir, windowManager)
applicationInfo = new JsonManager(path.join(pathToJsons, 'application.json')); db = new JsonDatabase<DatabaseScheme, any>(pathToDatabaseFile)
memoryManager = new MemoryManager(path.join(pathToJsons, 'memory.json'));
sendFileQueue = new QueueManager(path.join(pathToJsons, 'sendFileTasks.json'), compareFnFileItemTask);
workerManager = new WorkerManager(pathToWorkerDir, windowManager); await db.update((data) => {
data.app_config.server_found = false
data.app_config.logged_in = false
return data
})
await userConfig.writeValue('app_type', 'client'); startAnnouncementWatcher()
await applicationInfo.writeValue('users_ip', []); startResetApplicationWatcher()
await applicationInfo.writeValue('serverIp', '');
await applicationInfo.writeValue('announcement', '');
await applicationInfo.writeValue('reset_application_preferences', false);
await memoryManager.resetFile();
startAnnouncementWatcher(); workerManager.startNetworkScannerWorker(
startResetApplicationWatcher(); UDP_PORT,
TCP_PORT,
'login',
'uc_not_found',
'reset_database',
pathToDatabaseFile,
)
workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT)
workerManager.startNetworkScannerWorker(UDP_PORT, TCP_PORT, 'login', 'uc_not_found', 'reset_database', path.join(pathToJsons, 'userConfig.json'), path.join(pathToJsons, 'application.json')); registerIPCHandlers()
workerManager.startDirectoriesWatchersWorker(path.join(pathToJsons, 'memory.json'), path.join(pathToJsons, 'application.json'));
workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT);
workerManager.startResourceCoordinatorWorker( await windowManager.changeContent('welcome')
path.join(pathToJsons, 'userConfig.json'), })
path.join(pathToJsons, 'application.json'),
path.join(pathToJsons, 'memory.json'),
path.join(pathToJsons, 'sendFileTasks.json'),
TCP_PORT
);
registerIPCHandlers();
await windowManager.changeContent('welcome');
});
app.on('window-all-closed', async () => { app.on('window-all-closed', async () => {
console.log('All windows closed, starting cleanup...'); console.log('All windows closed, starting cleanup...')
await cleanupAndExit(); // Call cleanup when all windows are closed await cleanupAndExit() // Call cleanup when all windows are closed
}); })
// Catch CTRL+C (SIGINT) and clean up resources // Catch CTRL+C (SIGINT) and clean up resources
process.on('SIGINT', async () => { process.on('SIGINT', async () => {
console.log('CTRL+C pressed, starting cleanup...'); console.log('CTRL+C pressed, starting cleanup...')
await cleanupAndExit(); // Call cleanup on SIGINT await cleanupAndExit() // Call cleanup on SIGINT
}); })
app.on('before-quit', async () => { app.on('before-quit', async () => {
console.log('Application is quitting, starting cleanup...'); console.log('Application is quitting, starting cleanup...')
await cleanupAndExit(); // Call cleanup before app quit await cleanupAndExit() // Call cleanup before app quit
}); })
// Register IPC handlers // Register IPC ipc-handlers
function registerIPCHandlers() { function registerIPCHandlers() {
// Window Manager IPC Handlers
ipcMain.handle('show-alert', async (event: IpcMainInvokeEvent, message: string) => { ipcMain.handle('show-alert', async (event: IpcMainInvokeEvent, message: string) => {
if (!windowManager) throw new Error('WindowManager is not initialized.'); if (!windowManager) throw new Error('WindowManager is not initialized.')
await windowManager.showAlert(message); await windowManager.showAlert(message)
}); })
ipcMain.handle('start-workers', async (_event: IpcMainInvokeEvent) => {
if (!workerManager) throw new Error('WorkerManager is not initialized.')
workerManager.startDirectoriesWatchersWorker(pathToDatabaseFile)
workerManager.startResourceCoordinatorWorker(pathToDatabaseFile, TCP_PORT)
})
ipcMain.handle('stop-workers', async (_event: IpcMainInvokeEvent) => {
if (!workerManager) throw new Error('WorkerManager is not initialized.')
workerManager.closeAllWorkers()
workerManager.startNetworkScannerWorker(
UDP_PORT,
TCP_PORT,
'login',
'uc_not_found',
'reset_database',
pathToDatabaseFile,
)
workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT)
})
ipcMain.handle('change-content', async (_event: IpcMainInvokeEvent, destination: string) => { ipcMain.handle('change-content', async (_event: IpcMainInvokeEvent, destination: string) => {
if (!windowManager) throw new Error('WindowManager is not initialized.'); if (!windowManager) throw new Error('WindowManager is not initialized.')
await windowManager.changeContent(destination); await windowManager.changeContent(destination)
}); })
ipcMain.handle('select-directory', async (_event: IpcMainInvokeEvent) => { ipcMain.handle('select-directory', async (_event: IpcMainInvokeEvent) => {
if (!windowManager) throw new Error('WindowManager is not initialized.'); if (!windowManager) throw new Error('WindowManager is not initialized.')
return await windowManager.selectDirectory(); return await windowManager.selectDirectory()
}); })
ipcMain.handle('select-file', async (_event: IpcMainInvokeEvent) => { ipcMain.handle('select-file', async (_event: IpcMainInvokeEvent) => {
if (!windowManager) throw new Error('WindowManager is not initialized.'); if (!windowManager) throw new Error('WindowManager is not initialized.')
return await windowManager.selectFile(); return await windowManager.selectFile()
}); })
ipcMain.handle('show-file-in-explorer', async (_event: IpcMainInvokeEvent, path: string) => { ipcMain.handle('show-file-in-explorer', async (_event: IpcMainInvokeEvent, path: string) => {
if (!windowManager) throw new Error('WindowManager is not initialized.'); if (!windowManager) throw new Error('WindowManager is not initialized.')
return await windowManager.showFileInExplorer(path); return await windowManager.showFileInExplorer(path)
}); })
ipcMain.handle('read-announcement', async (_event: IpcMainInvokeEvent) => {
if (!db) throw new Error('Database is not initialized.')
const data = await db.read()
const announcement = data.network.announcement
data.network.announcement = ''
return announcement
})
ipcMain.handle('close-announcement-window', async (_event: IpcMainInvokeEvent) => { ipcMain.handle('close-announcement-window', async (_event: IpcMainInvokeEvent) => {
if (!windowManager) throw new Error('WindowManager is not initialized.'); if (!windowManager) throw new Error('WindowManager is not initialized.')
return await windowManager.closeAnnouncementWindow(); return await windowManager.closeAnnouncementWindow()
}); })
// TcpMethods IPC Handlers
ipcMain.handle('open-socket', async (_event: IpcMainInvokeEvent) => {
if (!applicationInfo) throw new Error('TcpMethods is not initialized.');
const serverIp = await applicationInfo.readValue('serverIp');
if (!serverIp) return;
tcpCommunicator = new TcpCommunicator(serverIp, TCP_PORT);
return await tcpCommunicator.connect()
});
ipcMain.handle('send-message', async (_event: IpcMainInvokeEvent, operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer) => {
if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.');
return await tcpCommunicator.sendMessage(operationCode, metaInfo, fileContent);
});
ipcMain.handle('has-response-arrived', async (_event: IpcMainInvokeEvent) => {
if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.');
return tcpCommunicator.hasResponseArrived();
});
ipcMain.handle('close-socket', async (_event: IpcMainInvokeEvent) => {
if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.');
return await tcpCommunicator.disconnect();
});
ipcMain.handle('get-last-result', async (_event: IpcMainInvokeEvent) => {
if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.');
return tcpCommunicator.getLastResult();
});
ipcMain.handle('get-operation-codes', () => {
return operationCodes;
});
// UserConfig IPC Handlers
ipcMain.handle('read-user-json-files', async (_event: IpcMainInvokeEvent, key: string) => {
if (!userConfig) throw new Error('UserConfig is not initialized.');
return await userConfig.readValue(key);
});
ipcMain.handle('write-user-json-files', async (_event: IpcMainInvokeEvent, key: string, value: any) => {
if (!userConfig) throw new Error('UserConfig is not initialized.');
return userConfig.writeValue(key, value);
});
ipcMain.handle('reset-user-json-files', async () => {
if (!userConfig) throw new Error('UserConfig is not initialized.');
return userConfig.resetFile();
});
ipcMain.handle('remove-user-json-files-key', async (_event: IpcMainInvokeEvent, key: string) => {
if (!userConfig) throw new Error('UserConfig is not initialized.');
return userConfig.removeValue(key);
});
// ApplicationPreferences IPC Handlers
ipcMain.handle('read-application-json-files', async (_event: IpcMainInvokeEvent, key: string) => {
if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.');
return await applicationInfo.readValue(key);
});
ipcMain.handle('write-application-json-files', async (_event: IpcMainInvokeEvent, key: string, value: any) => {
if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.');
return applicationInfo.writeValue(key, value);
});
ipcMain.handle('reset-application-json-files', async () => {
if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.');
const serverIp = await applicationInfo.readValue('serverIp');
await applicationInfo.resetFile();
if(serverIp) {
await applicationInfo.writeValue('serverIp', serverIp);
}
});
ipcMain.handle('remove-application-json-files-key', async (_event: IpcMainInvokeEvent, key: string) => {
if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.');
return applicationInfo.removeValue(key);
});
// Memory IPC Handlers
ipcMain.handle('memory-create-entry', async () => {
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
return memoryManager.storeMetaInformation({});
});
ipcMain.handle('memory-read-entry', async (_event: IpcMainInvokeEvent, id: string) => {
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
return memoryManager.retrieveMetaInformation(id);
});
ipcMain.handle('memory-update-entry', async (_event: IpcMainInvokeEvent, id: string, data: any) => {
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
return memoryManager.updateMetaInformation(id, data);
});
ipcMain.handle('memory-remove-entry', async (_event: IpcMainInvokeEvent, id: string) => {
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
return memoryManager.removeMetaInformation(id);
});
ipcMain.handle('memory-reset', async () => {
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
return memoryManager.resetFile();
});
// Queue IPC Handlers
ipcMain.handle('add-task-to-send-file-queue', async (event: IpcMainInvokeEvent, task: FileItemTask) => {
if (!sendFileQueue) throw new Error('SendFileQueue is not initialized.');
sendFileQueue.enqueue(task);
});
// Workers IPC Handlers // Workers IPC Handlers
ipcMain.handle('start-backup-retrieval', async (_event: IpcMainInvokeEvent, destinationPath: string) => { ipcMain.handle(
if (!workerManager) throw new Error('WorkerManager is not initialized.'); 'start-backup-retrieval',
return workerManager.startBackupRetrievalWorker( async (_event: IpcMainInvokeEvent, destinationPath: string) => {
path.join(pathToJsons, 'userConfig.json'), if (!workerManager) throw new Error('WorkerManager is not initialized.')
path.join(pathToJsons, 'application.json'), return workerManager.startBackupRetrievalWorker(TCP_PORT, destinationPath, pathToDatabaseFile)
TCP_PORT, },
destinationPath )
);
});
} }
+47 -36
View File
@@ -1,45 +1,56 @@
import { contextBridge, ipcRenderer } from 'electron'; import { contextBridge, ipcRenderer } from 'electron'
import {FileItemTask} from "../interfaces/file_item_task"; import { FileItemTask } from '../interfaces/file_item_task'
import { ipcDatabaseHandler } from '../ipc-handlers/database_handler'
import { EncryptionKeyScheme, UserInfoScheme } from '../database/schemes/app_config_scheme'
import { ipcUCHandler } from '../ipc-handlers/uc_handler'
import { ParsedMessage } from '../network/message_handler'
import { NetworkUserScheme } from '../database/schemes/network_scheme'
import { DirectoryInfo, DirectorySchemes } from "../database/schemes/local_resources_scheme";
contextBridge.exposeInMainWorld('electronAPI', { contextBridge.exposeInMainWorld('databaseAPI', {
// UserConfig methods getAppType: (): Promise<string> => ipcDatabaseHandler.getAppType(),
readUserConfig: (key: string): Promise<any> => ipcRenderer.invoke('read-user-json-files', key), getUserInfo: (): Promise<UserInfoScheme> => ipcDatabaseHandler.getUserInfo(),
writeUserConfig: (key: string, value: any): Promise<boolean> => ipcRenderer.invoke('write-user-json-files', key, value), getActiveUsers: (): Promise<NetworkUserScheme> => ipcDatabaseHandler.getActiveUsers(),
removeUserConfig: (key: string) : Promise<boolean> => ipcRenderer.invoke('remove-user-json-files', key), getLocalResources: (): Promise<DirectorySchemes> => ipcDatabaseHandler.getLocalResources(),
resetUserConfig: (): Promise<boolean> => ipcRenderer.invoke('reset-user-json-files'), getDirectoryInfo: (id: string): Promise<DirectoryInfo> => ipcDatabaseHandler.getDirectoryInfo(id),
setLoginStatus: (status: boolean): Promise<boolean> => ipcDatabaseHandler.setLoginStatus(status),
isBackupSet: (): Promise<boolean> => ipcDatabaseHandler.isBackupSet(),
writeUserInfo: (userInfo: UserInfoScheme): Promise<boolean> =>
ipcDatabaseHandler.writeUserInfo(userInfo),
writeEncryptionKey: (encryptionKey: EncryptionKeyScheme): Promise<boolean> =>
ipcDatabaseHandler.writeEncryptionKey(encryptionKey),
writeDirectoryPath: (id: string, path: string): Promise<boolean> => ipcDatabaseHandler.writeDirectoryPath(id, path),
addTaskToSendFileQueue: (task: FileItemTask): Promise<void> =>
ipcDatabaseHandler.addTaskToSendFileQueue(task),
resetInternalDatabase: (): Promise<boolean> => ipcDatabaseHandler.resetInternalDatabase(),
})
// ApplicationPreferences methods contextBridge.exposeInMainWorld('networkAPI', {
readApplicationInfo: (key: string): Promise<any> => ipcRenderer.invoke('read-application-json-files', key), openUcSocket: (): Promise<boolean> => ipcUCHandler.openUcSocket(),
writeApplicationInfo: (key: string, value: any): Promise<boolean> => ipcRenderer.invoke('write-application-json-files', key, value), sendUcMessage: (operationCode: string, metaInfo: any, fileContent: any): Promise<boolean> =>
removeApplicationInfo: (key: string) : Promise<boolean> => ipcRenderer.invoke('remove-application-preferences', key), ipcUCHandler.sendUcMessage(operationCode, metaInfo, fileContent),
resetApplicationInfo: (): Promise<boolean> => ipcRenderer.invoke('reset-application-json-files'), closeUcSocket: (): Promise<boolean> => ipcUCHandler.closeUcSocket(),
hasResponseArrived: (): Promise<boolean> => ipcUCHandler.hasResponseArrived(),
getLastUcResult: (): Promise<ParsedMessage | null> => ipcUCHandler.getLastUcResult(),
getOperationsCodes: (): Promise<{ [key: string]: string }> => ipcUCHandler.getOperationsCodes(),
})
// UcCommunication methods contextBridge.exposeInMainWorld('uiAPI', {
openUcSocket: (): Promise<any> => ipcRenderer.invoke('open-socket'), startWorkers: (): Promise<void> => ipcRenderer.invoke('start-workers'),
sendUcMessage: (operationCode: string, metaInfo: any, fileContent: any): Promise<any> => ipcRenderer.invoke('send-message', operationCode, metaInfo, fileContent), stopWorkers: (): Promise<void> => ipcRenderer.invoke('stop-workers'),
closeUcSocket: (): Promise<any> => ipcRenderer.invoke('close-socket'),
hasResponseArrived: (): Promise<boolean> => ipcRenderer.invoke('has-response-arrived'),
getLastUcResult: (): Promise<any> => ipcRenderer.invoke('get-last-result'),
getOperationsCodes: (): Promise<{ data: { [key: string]: string } }> => ipcRenderer.invoke('get-operation-codes'),
// MemoryManager methods
createMemoryEntry: (): Promise<string> => ipcRenderer.invoke('memory-create-entry'),
readMemoryEntry: (id: string): Promise<any> => ipcRenderer.invoke('memory-read-entry', id),
updateMemoryEntry: (id: string, data: any): Promise<boolean> => ipcRenderer.invoke('memory-update-entry', id, data),
removeMemoryEntry: (id: string): Promise<boolean> => ipcRenderer.invoke('memory-remove-entry', id),
resetMemory: (): Promise<boolean> => ipcRenderer.invoke('memory-reset'),
// UI methods
showAlert: (message: string): Promise<void> => ipcRenderer.invoke('show-alert', message), showAlert: (message: string): Promise<void> => ipcRenderer.invoke('show-alert', message),
changeContent: (destination: string): Promise<void> => ipcRenderer.invoke('change-content', destination), changeContent: (destination: string): Promise<void> =>
ipcRenderer.invoke('change-content', destination),
selectDirectory: (): Promise<string | undefined> => ipcRenderer.invoke('select-directory'), selectDirectory: (): Promise<string | undefined> => ipcRenderer.invoke('select-directory'),
selectFile: (): Promise<string | undefined> => ipcRenderer.invoke('select-file'), selectFile: (): Promise<string | undefined> => ipcRenderer.invoke('select-file'),
showFileInExplorer: (path: string): Promise<void> => ipcRenderer.invoke('show-file-in-explorer', path), showFileInExplorer: (path: string): Promise<void> =>
ipcRenderer.invoke('show-file-in-explorer', path),
readAnnouncement: (): Promise<string> => ipcRenderer.invoke('read-announcement'),
closeAnnouncementWindow: (): Promise<void> => ipcRenderer.invoke('close-announcement-window'), closeAnnouncementWindow: (): Promise<void> => ipcRenderer.invoke('close-announcement-window'),
})
// Queue methods contextBridge.exposeInMainWorld('electronAPI', {
addTaskToSendFileQueue: (task: FileItemTask): Promise<void> => ipcRenderer.invoke('add-task-to-send-file-queue', task),
// Workers // Workers
startBackupRetrieval: (destinationPath: string): Promise<void> => ipcRenderer.invoke('start-backup-retrieval', destinationPath), startBackupRetrieval: (destinationPath: string): Promise<void> =>
}); ipcRenderer.invoke('start-backup-retrieval', destinationPath),
})
+13 -13
View File
@@ -1,43 +1,43 @@
import { SocketCommunicatorBase } from "./socket_communicator/socket_communicator_base"; import { SocketCommunicatorBase } from './socket_communicator/socket_communicator_base'
interface Connection { interface Connection {
communicator: SocketCommunicatorBase; communicator: SocketCommunicatorBase
} }
export class ConnectionManager { export class ConnectionManager {
private readonly connections: { [key: string]: Connection }; private readonly connections: { [key: string]: Connection }
constructor() { constructor() {
this.connections = {}; this.connections = {}
} }
// Adds a new communicator, keyed by both IP and port // Adds a new communicator, keyed by both IP and port
addConnection(ip: string, port: number, communicator: SocketCommunicatorBase): void { addConnection(ip: string, port: number, communicator: SocketCommunicatorBase): void {
const key = `${ip}:${port}`; const key = `${ip}:${port}`
// Store the communicator along with the client's public and private keys // Store the communicator along with the client's public and private keys
this.connections[key] = { this.connections[key] = {
communicator communicator,
}; }
} }
// Removes a communicator based on IP and port // Removes a communicator based on IP and port
removeCommunicator(ip: string, port: number): void { removeCommunicator(ip: string, port: number): void {
const key = `${ip}:${port}`; const key = `${ip}:${port}`
if (this.connections[key]) { if (this.connections[key]) {
delete this.connections[key]; delete this.connections[key]
} }
} }
// Retrieves a communicator based on IP and port // Retrieves a communicator based on IP and port
getCommunicator(ip: string, port: number): SocketCommunicatorBase | null { getCommunicator(ip: string, port: number): SocketCommunicatorBase | null {
const key = `${ip}:${port}`; const key = `${ip}:${port}`
return this.connections[key] ? this.connections[key].communicator : null; return this.connections[key] ? this.connections[key].communicator : null
} }
// Checks if a communicator exists for a given IP and port // Checks if a communicator exists for a given IP and port
communicatorExists(ip: string, port: number): boolean { communicatorExists(ip: string, port: number): boolean {
const key = `${ip}:${port}`; const key = `${ip}:${port}`
return this.connections[key] !== undefined; return this.connections[key] !== undefined
} }
} }
+18 -18
View File
@@ -1,7 +1,7 @@
export interface ParsedMessage { export interface ParsedMessage {
operationCode: string; operationCode: string
metaInfo?: { [key: string]: any }; metaInfo?: { [key: string]: any }
fileContent?: Buffer; fileContent?: Buffer
} }
export class MessageHandler { export class MessageHandler {
@@ -9,57 +9,57 @@ export class MessageHandler {
static formatMessage( static formatMessage(
operationCode: string, operationCode: string,
metaInfo?: { [key: string]: any }, metaInfo?: { [key: string]: any },
fileContent?: Buffer fileContent?: Buffer,
): string { ): string {
let message = `${operationCode}\n`; // First part: operationCode and guid let message = `${operationCode}\n` // First part: operationCode and guid
if (metaInfo && Object.keys(metaInfo).length > 0) { if (metaInfo && Object.keys(metaInfo).length > 0) {
message += `${JSON.stringify(metaInfo)}\n`; // Add metaInfo message += `${JSON.stringify(metaInfo)}\n` // Add metaInfo
} }
if (fileContent && fileContent.length > 0) { if (fileContent && fileContent.length > 0) {
message += fileContent.toString('base64'); // Convert buffer to Base64 for fileContent message += fileContent.toString('base64') // Convert buffer to Base64 for fileContent
} }
return message; return message
} }
// Parse the incoming message (convert Base64 back to Buffer if fileContent is present) // Parse the incoming message (convert Base64 back to Buffer if fileContent is present)
static parseMessage(msg: string): ParsedMessage { static parseMessage(msg: string): ParsedMessage {
const parts = msg.split('\n'); // Split by \n (operationCode, metaInfo, and fileContent are on separate lines) const parts = msg.split('\n') // Split by \n (operationCode, metaInfo, and fileContent are on separate lines)
// First part should always be the operation code // First part should always be the operation code
const operationCode = parts[0]?.trim(); const operationCode = parts[0]?.trim()
if (!operationCode) { if (!operationCode) {
throw new Error('Missing operation code in the message'); throw new Error('Missing operation code in the message')
} }
let metaInfo: { [key: string]: any } | undefined = undefined; let metaInfo: { [key: string]: any } | undefined = undefined
let fileContent: Buffer | undefined = undefined; let fileContent: Buffer | undefined = undefined
// Parse the metaInfo (JSON object) if present // Parse the metaInfo (JSON object) if present
if (parts[1]) { if (parts[1]) {
try { try {
metaInfo = JSON.parse(parts[1].trim()); metaInfo = JSON.parse(parts[1].trim())
} catch (err) { } catch (err) {
console.error('Invalid metaInfo JSON format:', err); console.error('Invalid metaInfo JSON format:', err)
} }
} }
// Convert Base64 string back to Buffer for fileContent if present // Convert Base64 string back to Buffer for fileContent if present
if (parts[2]) { if (parts[2]) {
fileContent = Buffer.from(parts[2].trim(), 'base64'); fileContent = Buffer.from(parts[2].trim(), 'base64')
} }
return { return {
operationCode, operationCode,
metaInfo, metaInfo,
fileContent, fileContent,
}; }
} }
// Validate if the parsed message contains an operation code // Validate if the parsed message contains an operation code
static validateMessage(parsedMessage: ParsedMessage | null): boolean { static validateMessage(parsedMessage: ParsedMessage | null): boolean {
return !!parsedMessage?.operationCode; return !!parsedMessage?.operationCode
} }
} }
+1 -1
View File
@@ -1,2 +1,2 @@
export { UdpClient} from './udp/udp_client'; export { UdpClient } from './udp/udp_client'
export { TcpClient } from './tcp/tcp_client' export { TcpClient } from './tcp/tcp_client'
+2 -1
View File
@@ -15,6 +15,7 @@ export let operationCodes = {
LOGIN: 'LOGIN', LOGIN: 'LOGIN',
SIGN_UP: 'SIGN_UP', SIGN_UP: 'SIGN_UP',
RESET_PASSWORD: 'RESET_PASSWORD', RESET_PASSWORD: 'RESET_PASSWORD',
EMAIL_VERIFICATION: 'EMAIL_VERIFICATION',
FIND_BY_EMAIL: 'FIND_BY_EMAIL', FIND_BY_EMAIL: 'FIND_BY_EMAIL',
MODIFY_USER: 'MODIFY_USER', MODIFY_USER: 'MODIFY_USER',
@@ -38,4 +39,4 @@ export let operationCodes = {
GET_BACKUP_STRUCTURE: 'GET_BACKUP_STRUCTURE', GET_BACKUP_STRUCTURE: 'GET_BACKUP_STRUCTURE',
REQ_FILE_FROM_BACKUP: 'REQ_FILE_FROM_BACKUP', REQ_FILE_FROM_BACKUP: 'REQ_FILE_FROM_BACKUP',
DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE', DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE',
}; }
@@ -1,44 +1,44 @@
// operation_handler.ts // operation_handler.ts
import { ParsedMessage, MessageHandler } from '../message_handler'; import { ParsedMessage, MessageHandler } from '../message_handler'
import { OperationPlugin } from './operation_plugin'; import { OperationPlugin } from './operation_plugin'
// Define handler function type to return Promise<ParsedMessage> // Define handler function type to return Promise<ParsedMessage>
type OperationHandlerFunction = (parsedMessage: ParsedMessage) => Promise<ParsedMessage>; type OperationHandlerFunction = (parsedMessage: ParsedMessage) => Promise<ParsedMessage>
export class OperationHandler { export class OperationHandler {
private static instance: OperationHandler; private static instance: OperationHandler
private handlers: { [operationCode: string]: OperationHandlerFunction } = {}; private handlers: { [operationCode: string]: OperationHandlerFunction } = {}
private constructor() { private constructor() {
this.registerHandler('UNKNOWN_COMMAND', this.handleUnknownCommand); this.registerHandler('UNKNOWN_COMMAND', this.handleUnknownCommand)
} }
// Singleton instance // Singleton instance
public static getInstance(): OperationHandler { public static getInstance(): OperationHandler {
if (!OperationHandler.instance) { if (!OperationHandler.instance) {
OperationHandler.instance = new OperationHandler(); OperationHandler.instance = new OperationHandler()
} }
return OperationHandler.instance; return OperationHandler.instance
} }
// Register a handler for a specific operation code // Register a handler for a specific operation code
public registerHandler(operationCode: string, handler: OperationHandlerFunction): void { public registerHandler(operationCode: string, handler: OperationHandlerFunction): void {
this.handlers[operationCode] = handler; this.handlers[operationCode] = handler
} }
// Handle operation request asynchronously // Handle operation request asynchronously
public async handleOperation(rawMessage: string): Promise<ParsedMessage> { public async handleOperation(rawMessage: string): Promise<ParsedMessage> {
const parsedMessage = MessageHandler.parseMessage(rawMessage); const parsedMessage = MessageHandler.parseMessage(rawMessage)
if (!parsedMessage || !MessageHandler.validateMessage(parsedMessage)) { if (!parsedMessage || !MessageHandler.validateMessage(parsedMessage)) {
return this.handleUnknownCommand(parsedMessage); return this.handleUnknownCommand(parsedMessage)
} }
// Retrieve the handler for the operation code and invoke it asynchronously // Retrieve the handler for the operation code and invoke it asynchronously
const handler = this.handlers[parsedMessage.operationCode]; const handler = this.handlers[parsedMessage.operationCode]
if (handler) { if (handler) {
return await handler(parsedMessage); return await handler(parsedMessage)
} else { } else {
return this.handleUnknownCommand(parsedMessage); return this.handleUnknownCommand(parsedMessage)
} }
} }
@@ -47,11 +47,11 @@ export class OperationHandler {
return { return {
operationCode: 'UNKNOWN_COMMAND', operationCode: 'UNKNOWN_COMMAND',
metaInfo: { message: 'Unknown command received.' }, metaInfo: { message: 'Unknown command received.' },
}; }
} }
// Plugin system: Load plugins to register handlers // Plugin system: Load plugins to register ipc-handlers
public loadPlugin(plugin: OperationPlugin): void { public loadPlugin(plugin: OperationPlugin): void {
plugin.register(this); plugin.register(this)
} }
} }
@@ -1,6 +1,6 @@
// operation_plugin.ts // operation_plugin.ts
import { OperationHandler } from './operation_handler'; import { OperationHandler } from './operation_handler'
export interface OperationPlugin { export interface OperationPlugin {
register(operationHandler: OperationHandler): void; register(operationHandler: OperationHandler): void
} }
@@ -1,7 +1,7 @@
import { ParsedMessage } from '../message_handler'; import { ParsedMessage } from '../message_handler'
import { OperationHandler } from '../operations_base/operation_handler'; import { OperationHandler } from '../operations_base/operation_handler'
import os from 'node:os'; import os from 'node:os'
import {OperationPlugin} from "../operations_base/operation_plugin"; import { OperationPlugin } from '../operations_base/operation_plugin'
export class GeneralOperations implements OperationPlugin { export class GeneralOperations implements OperationPlugin {
public static readonly operationCodes = { public static readonly operationCodes = {
@@ -11,78 +11,93 @@ export class GeneralOperations implements OperationPlugin {
ALIVE: 'ALIVE', ALIVE: 'ALIVE',
SET_PUBLIC_KEY: 'SET_PUBLIC_KEY', SET_PUBLIC_KEY: 'SET_PUBLIC_KEY',
SET_AES_KEY: 'SET_AES_KEY', SET_AES_KEY: 'SET_AES_KEY',
}; }
// Handle heartbeat operation asynchronously // Handle heartbeat operation asynchronously
public static async handleAreYouHuman(): Promise<ParsedMessage> { public static async handleAreYouHuman(): Promise<ParsedMessage> {
const networkInterfaces = os.networkInterfaces(); const networkInterfaces = os.networkInterfaces()
let ipAddress = 'Unknown'; let ipAddress = 'Unknown'
for (const iface of Object.values(networkInterfaces)) { for (const iface of Object.values(networkInterfaces)) {
for (const address of iface!) { for (const address of iface!) {
if (address.family === 'IPv4' && !address.internal) { if (address.family === 'IPv4' && !address.internal) {
ipAddress = address.address; ipAddress = address.address
break; break
} }
} }
if (ipAddress !== 'Unknown') break; if (ipAddress !== 'Unknown') break
} }
return { return {
operationCode: GeneralOperations.operationCodes.ALIVE, operationCode: GeneralOperations.operationCodes.ALIVE,
metaInfo: { ipAddress }, metaInfo: { ipAddress },
}; }
} }
// Handle public key exchange asynchronously // Handle public key exchange asynchronously
public static async handlePublicKey(parsedMessage: ParsedMessage): Promise<ParsedMessage> { public static async handlePublicKey(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
const clientPublicKey = parsedMessage.metaInfo?.publicKey; const clientPublicKey = parsedMessage.metaInfo?.publicKey
if (clientPublicKey) { if (clientPublicKey) {
return { return {
operationCode: GeneralOperations.operationCodes.SET_PUBLIC_KEY, operationCode: GeneralOperations.operationCodes.SET_PUBLIC_KEY,
metaInfo: { publicKey: clientPublicKey }, metaInfo: { publicKey: clientPublicKey },
}; }
} else { } else {
return { return {
operationCode: GeneralOperations.operationCodes.ERR, operationCode: GeneralOperations.operationCodes.ERR,
metaInfo: { message: 'No public key provided.' }, metaInfo: { message: 'No public key provided.' },
}; }
} }
} }
// Handle AES key exchange asynchronously // Handle AES key exchange asynchronously
public static async handleAESKey(parsedMessage: ParsedMessage): Promise<ParsedMessage> { public static async handleAESKey(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
const aesKey = parsedMessage.metaInfo?.aesKey; const aesKey = parsedMessage.metaInfo?.aesKey
const aesIv = parsedMessage.metaInfo?.aesIv; const aesIv = parsedMessage.metaInfo?.aesIv
if (aesKey && aesIv) { if (aesKey && aesIv) {
return { return {
operationCode: GeneralOperations.operationCodes.SET_AES_KEY, operationCode: GeneralOperations.operationCodes.SET_AES_KEY,
metaInfo: { aesKey, aesIv }, metaInfo: { aesKey, aesIv },
}; }
} else { } else {
return { return {
operationCode: GeneralOperations.operationCodes.ERR, operationCode: GeneralOperations.operationCodes.ERR,
metaInfo: { message: 'No AES key provided.' }, metaInfo: { message: 'No AES key provided.' },
}; }
} }
} }
// Default async handler for OK operation // Default async handler for OK operation
public static async handleOk(parsedMessage: ParsedMessage): Promise<ParsedMessage> { public static async handleOk(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
return parsedMessage; // Acknowledge with OK, returning as-is return parsedMessage // Acknowledge with OK, returning as-is
} }
// Default async handler for ERR operation // Default async handler for ERR operation
public static async handleErr(parsedMessage: ParsedMessage): Promise<ParsedMessage> { public static async handleErr(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
return parsedMessage; // Log the error and return return parsedMessage // Log the error and return
} }
// Register general operations with the OperationHandler // Register general operations with the OperationHandler
public register(operationHandler: OperationHandler): void { public register(operationHandler: OperationHandler): void {
operationHandler.registerHandler(GeneralOperations.operationCodes.ARE_YOU_HUMAN, GeneralOperations.handleAreYouHuman); operationHandler.registerHandler(
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_PUBLIC_KEY, GeneralOperations.handlePublicKey); GeneralOperations.operationCodes.ARE_YOU_HUMAN,
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_AES_KEY, GeneralOperations.handleAESKey); GeneralOperations.handleAreYouHuman,
operationHandler.registerHandler(GeneralOperations.operationCodes.OK, GeneralOperations.handleOk); )
operationHandler.registerHandler(GeneralOperations.operationCodes.ERR, GeneralOperations.handleErr); operationHandler.registerHandler(
GeneralOperations.operationCodes.SET_PUBLIC_KEY,
GeneralOperations.handlePublicKey,
)
operationHandler.registerHandler(
GeneralOperations.operationCodes.SET_AES_KEY,
GeneralOperations.handleAESKey,
)
operationHandler.registerHandler(
GeneralOperations.operationCodes.OK,
GeneralOperations.handleOk,
)
operationHandler.registerHandler(
GeneralOperations.operationCodes.ERR,
GeneralOperations.handleErr,
)
} }
} }
@@ -1,11 +1,11 @@
import { ParsedMessage } from '../message_handler'; import { ParsedMessage } from '../message_handler'
import { OperationHandler } from '../operations_base/operation_handler'; import { OperationHandler } from '../operations_base/operation_handler'
import {operationCodes} from "../operation_codes"; import { operationCodes } from '../operation_codes'
import path from 'path'; import path from 'path'
import fs from 'fs/promises'; import fs from 'fs/promises'
import checkDiskSpace from "check-disk-space"; import checkDiskSpace from 'check-disk-space'
import { JsonManager } from '../../helpers/json_manager'; import { JsonManager } from '../../helpers/json_manager'
import {OperationPlugin} from "../operations_base/operation_plugin"; import { OperationPlugin } from '../operations_base/operation_plugin'
export class UserToUserOperations implements OperationPlugin { export class UserToUserOperations implements OperationPlugin {
public static readonly operationCodes = { public static readonly operationCodes = {
@@ -20,145 +20,209 @@ export class UserToUserOperations implements OperationPlugin {
GET_BACKUP_STRUCTURE: 'GET_BACKUP_STRUCTURE', GET_BACKUP_STRUCTURE: 'GET_BACKUP_STRUCTURE',
REQ_FILE_FROM_BACKUP: 'REQ_FILE_FROM_BACKUP', REQ_FILE_FROM_BACKUP: 'REQ_FILE_FROM_BACKUP',
DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE', DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE',
}; }
private static async hasEnoughDiskSpace(directory: string, requiredPercentage: number = 25): Promise<boolean> { private static async hasEnoughDiskSpace(
directory: string,
requiredPercentage: number = 25,
): Promise<boolean> {
try { try {
const diskInfo = await checkDiskSpace(directory); const diskInfo = await checkDiskSpace(directory)
const availableSpace = diskInfo.free; const availableSpace = diskInfo.free
const totalSpace = diskInfo.size; const totalSpace = diskInfo.size
const availablePercentage = (availableSpace / totalSpace) * 100; const availablePercentage = (availableSpace / totalSpace) * 100
return availablePercentage >= requiredPercentage; return availablePercentage >= requiredPercentage
} catch (error) { } catch (error) {
console.error(`Error checking disk space: ${error}`); console.error(`Error checking disk space: ${error}`)
return false; return false
} }
} }
public static async handleSendAnnouncement(parsedMessage: ParsedMessage): Promise<ParsedMessage> { public static async handleSendAnnouncement(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
if (!parsedMessage.metaInfo?.message) { if (!parsedMessage.metaInfo?.message) {
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing announcement message.' }}; return {
} operationCode: operationCodes.ERR,
metaInfo: { message: 'Missing announcement message.' },
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json'));
try {
await jsonManager.writeValue('announcement', parsedMessage.metaInfo.message);
console.log(`Announcement saved: ${parsedMessage.metaInfo.message}`);
return { operationCode: operationCodes.OK, metaInfo: { message: 'Announcement saved.' }};
} catch (error: any) {
console.error(`Error saving announcement: ${error.message}`);
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` }};
} }
} }
public static async handleGetUserInformation(parsedMessage: ParsedMessage): Promise<ParsedMessage> { const jsonManager = new JsonManager(
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'userConfig.json')); path.join(__dirname, '..', '..', 'json_files', 'application.json'),
)
try { try {
const userInfo = await jsonManager.readValue('user_info'); await jsonManager.writeValue('announcement', parsedMessage.metaInfo.message)
return { operationCode: operationCodes.OK, metaInfo: userInfo }; console.log(`Announcement saved: ${parsedMessage.metaInfo.message}`)
return { operationCode: operationCodes.OK, metaInfo: { message: 'Announcement saved.' } }
} catch (error: any) { } catch (error: any) {
console.error(`Error fetching user info: ${error.message}`); console.error(`Error saving announcement: ${error.message}`)
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Error fetching user info' }}; return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` } }
}
}
public static async handleGetUserInformation(
parsedMessage: ParsedMessage,
): Promise<ParsedMessage> {
const jsonManager = new JsonManager(
path.join(__dirname, '..', '..', 'json_files', 'userConfig.json'),
)
try {
const userInfo = await jsonManager.readValue('user_info')
return { operationCode: operationCodes.OK, metaInfo: userInfo }
} catch (error: any) {
console.error(`Error fetching user info: ${error.message}`)
return {
operationCode: operationCodes.ERR,
metaInfo: { message: 'Error fetching user info' },
}
} }
} }
public static async handleBackupFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> { public static async handleBackupFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
if (!parsedMessage.metaInfo?.userName || !parsedMessage.metaInfo?.relativeFilePath || !parsedMessage.fileContent) { if (
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing file or user information.' }}; !parsedMessage.metaInfo?.userName ||
!parsedMessage.metaInfo?.relativeFilePath ||
!parsedMessage.fileContent
) {
return {
operationCode: operationCodes.ERR,
metaInfo: { message: 'Missing file or user information.' },
}
} }
const { userName, relativeFilePath } = parsedMessage.metaInfo; const { userName, relativeFilePath } = parsedMessage.metaInfo
const fullFilePath = path.join(__dirname, '..', '..', 'backups', userName, relativeFilePath); const fullFilePath = path.join(__dirname, '..', '..', 'backups', userName, relativeFilePath)
try { try {
if (!await UserToUserOperations.hasEnoughDiskSpace(path.dirname(fullFilePath), 25)) { if (!(await UserToUserOperations.hasEnoughDiskSpace(path.dirname(fullFilePath), 25))) {
return { operationCode: operationCodes.ERR, metaInfo: return {
{ message: 'Insufficient disk space.' }}; operationCode: operationCodes.ERR,
metaInfo: { message: 'Insufficient disk space.' },
}
} }
await fs.mkdir(path.dirname(fullFilePath), { recursive: true }); await fs.mkdir(path.dirname(fullFilePath), { recursive: true })
await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer); await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer)
console.log(`File saved: ${fullFilePath}`); console.log(`File saved: ${fullFilePath}`)
return { operationCode: operationCodes.OK, metaInfo: { message: `File saved: ${relativeFilePath}` }}; return {
operationCode: operationCodes.OK,
metaInfo: { message: `File saved: ${relativeFilePath}` },
}
} catch (error: any) { } catch (error: any) {
console.error(`Error saving file: ${error.message}`); console.error(`Error saving file: ${error.message}`)
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error saving file: ${error.message}` }}; return {
operationCode: operationCodes.ERR,
metaInfo: { message: `Error saving file: ${error.message}` },
}
} }
} }
public static async handleClearBackup(parsedMessage: ParsedMessage): Promise<ParsedMessage> { public static async handleClearBackup(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
if (!parsedMessage.metaInfo?.userName) { if (!parsedMessage.metaInfo?.userName) {
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' }}; return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' } }
} }
const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.userName); const userBackupDir = path.join(
__dirname,
'..',
'..',
'backups',
parsedMessage.metaInfo.userName,
)
try { try {
await fs.rm(userBackupDir, { recursive: true, force: true }); await fs.rm(userBackupDir, { recursive: true, force: true })
console.log(`Backup cleared: ${userBackupDir}`); console.log(`Backup cleared: ${userBackupDir}`)
return { operationCode: operationCodes.OK, metaInfo: { message: `Backup cleared for ${parsedMessage.metaInfo.userName}` }}; return {
operationCode: operationCodes.OK,
metaInfo: { message: `Backup cleared for ${parsedMessage.metaInfo.userName}` },
}
} catch (error: any) { } catch (error: any) {
console.error(`Error clearing backup: ${error.message}`); console.error(`Error clearing backup: ${error.message}`)
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error clearing backup: ${error.message}` }}; return {
operationCode: operationCodes.ERR,
metaInfo: { message: `Error clearing backup: ${error.message}` },
}
} }
} }
public static async handleShareFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> { public static async handleShareFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
if (!parsedMessage.metaInfo?.userName || !parsedMessage.metaInfo?.relativeFilePath || !parsedMessage.fileContent) { if (
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing share info.' }}; !parsedMessage.metaInfo?.userName ||
!parsedMessage.metaInfo?.relativeFilePath ||
!parsedMessage.fileContent
) {
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing share info.' } }
} }
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json')); const jsonManager = new JsonManager(
const { userName, relativeFilePath } = parsedMessage.metaInfo; path.join(__dirname, '..', '..', 'json_files', 'application.json'),
)
const { userName, relativeFilePath } = parsedMessage.metaInfo
try { try {
const appInfo = await jsonManager.readValue('shareDirectory'); const appInfo = await jsonManager.readValue('shareDirectory')
const shareDirectory = appInfo?.path || ''; const shareDirectory = appInfo?.path || ''
console.log(`\n\nShare directory: ${shareDirectory}\n\n`); console.log(`\n\nShare directory: ${shareDirectory}\n\n`)
if (!shareDirectory) { if (!shareDirectory) {
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Share directory missing.' }}; return {
operationCode: operationCodes.ERR,
metaInfo: { message: 'Share directory missing.' },
}
} }
const fullFilePath = path.join(shareDirectory, userName, relativeFilePath); const fullFilePath = path.join(shareDirectory, userName, relativeFilePath)
await fs.mkdir(path.dirname(fullFilePath), { recursive: true }); await fs.mkdir(path.dirname(fullFilePath), { recursive: true })
await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer); await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer)
console.log(`File shared: ${fullFilePath}`); console.log(`File shared: ${fullFilePath}`)
return { operationCode: operationCodes.OK, metaInfo: { message: `File shared: ${relativeFilePath}` }}; return {
operationCode: operationCodes.OK,
metaInfo: { message: `File shared: ${relativeFilePath}` },
}
} catch (error: any) { } catch (error: any) {
console.error(`Error sharing file: ${error.message}`); console.error(`Error sharing file: ${error.message}`)
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error sharing file: ${error.message}` }}; return {
operationCode: operationCodes.ERR,
metaInfo: { message: `Error sharing file: ${error.message}` },
}
} }
} }
public static async handleClearDepartment(parsedMessage: ParsedMessage): Promise<ParsedMessage> { public static async handleClearDepartment(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
if (!parsedMessage.metaInfo?.userName) { if (!parsedMessage.metaInfo?.userName) {
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' }}; return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' } }
} }
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json')); const jsonManager = new JsonManager(
const { userName } = parsedMessage.metaInfo; path.join(__dirname, '..', '..', 'json_files', 'application.json'),
)
const { userName } = parsedMessage.metaInfo
try { try {
const appInfo = await jsonManager.readValue('departmentDirectory'); const appInfo = await jsonManager.readValue('departmentDirectory')
const departmentDir = appInfo?.path || ''; const departmentDir = appInfo?.path || ''
const userDepartmentDir = path.join(departmentDir, '..', 'DEPARTMENT_SHARE', userName); const userDepartmentDir = path.join(departmentDir, '..', 'DEPARTMENT_SHARE', userName)
try { try {
await fs.access(userDepartmentDir) await fs.access(userDepartmentDir)
} catch (ex: any) { } catch (ex: any) {
return { operationCode: operationCodes.OK}; return { operationCode: operationCodes.OK }
} }
await fs.rm(userDepartmentDir, { recursive: true, force: true }); await fs.rm(userDepartmentDir, { recursive: true, force: true })
console.log(`Department backup cleared: ${userDepartmentDir}`); console.log(`Department backup cleared: ${userDepartmentDir}`)
return { operationCode: operationCodes.OK, metaInfo: { message: `Department backup cleared for ${userName}` }}; return {
operationCode: operationCodes.OK,
metaInfo: { message: `Department backup cleared for ${userName}` },
}
} catch (error: any) { } catch (error: any) {
console.error(`Error clearing department: ${error.message}`); console.error(`Error clearing department: ${error.message}`)
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error clearing department: ${error.message}` }}; return {
operationCode: operationCodes.ERR,
metaInfo: { message: `Error clearing department: ${error.message}` },
}
} }
} }
@@ -168,151 +232,203 @@ export class UserToUserOperations implements OperationPlugin {
return { return {
operationCode: operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: 'Missing file content or meta information.' }, metaInfo: { message: 'Missing file content or meta information.' },
}; }
} }
const { userName, relativeFilePath } = parsedMessage.metaInfo; const { userName, relativeFilePath } = parsedMessage.metaInfo
if (!userName || !relativeFilePath) { if (!userName || !relativeFilePath) {
return { return {
operationCode: operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: 'Missing user name or file path information.' }, metaInfo: { message: 'Missing user name or file path information.' },
}; }
} }
// Path to the application.json to read the shareDirectory field // Path to the application.json to read the shareDirectory field
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json'); const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json')
const jsonManager = new JsonManager(pathToApplicationJson); const jsonManager = new JsonManager(pathToApplicationJson)
// Read application configuration asynchronously // Read application configuration asynchronously
let appInfo; let appInfo
try { try {
appInfo = await jsonManager.readValue('departmentDirectory'); appInfo = await jsonManager.readValue('departmentDirectory')
} catch (error: any) { } catch (error: any) {
return { return {
operationCode: operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: `Error reading application config: ${error.message}` }, metaInfo: { message: `Error reading application config: ${error.message}` },
}; }
} }
if (!appInfo || !appInfo.path) { if (!appInfo || !appInfo.path) {
return { return {
operationCode: operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: 'Error retrieving department directory from application.json.' }, metaInfo: { message: 'Error retrieving department directory from application.json.' },
}; }
} }
// Get the share directory path // Get the share directory path
const departmentDirectory = appInfo.path; const departmentDirectory = appInfo.path
const fullFilePath = path.join(departmentDirectory, '..', 'DEPARTMENT_FILES', userName, relativeFilePath); const fullFilePath = path.join(
departmentDirectory,
'..',
'DEPARTMENT_FILES',
userName,
relativeFilePath,
)
try { try {
// Ensure the directory structure exists (create directories if they don't exist) // Ensure the directory structure exists (create directories if they don't exist)
const dirPath = path.dirname(fullFilePath); const dirPath = path.dirname(fullFilePath)
await fs.mkdir(dirPath, { recursive: true }); await fs.mkdir(dirPath, { recursive: true })
// Write the file content to the specified path // Write the file content to the specified path
await fs.writeFile(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64'); await fs.writeFile(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64')
return { return {
operationCode: operationCodes.OK, operationCode: operationCodes.OK,
metaInfo: { message: `File shared successfully: ${relativeFilePath}` }, metaInfo: { message: `File shared successfully: ${relativeFilePath}` },
}; }
} catch (error: any) { } catch (error: any) {
return { return {
operationCode: operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: `Error sharing file: ${error.message}` }, metaInfo: { message: `Error sharing file: ${error.message}` },
}; }
} }
} }
public static async handleIsBackupCreated(parsedMessage: ParsedMessage): Promise<ParsedMessage> { public static async handleIsBackupCreated(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
if (!parsedMessage.metaInfo?.name) { if (!parsedMessage.metaInfo?.name) {
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing name in meta information.' }}; return {
operationCode: operationCodes.ERR,
metaInfo: { message: 'Missing name in meta information.' },
}
} }
const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name); const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name)
try { try {
const exists = await fs.access(userBackupDir).then(() => true).catch(() => false); const exists = await fs
return { operationCode: operationCodes.OK, metaInfo: { backupExists: exists }}; .access(userBackupDir)
.then(() => true)
.catch(() => false)
return { operationCode: operationCodes.OK, metaInfo: { backupExists: exists } }
} catch (error: any) { } catch (error: any) {
console.error(`Error checking backup: ${error.message}`); console.error(`Error checking backup: ${error.message}`)
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error checking backup: ${error.message}` }}; return {
operationCode: operationCodes.ERR,
metaInfo: { message: `Error checking backup: ${error.message}` },
}
} }
} }
public static async handleGetBackupStructure(parsedMessage: ParsedMessage): Promise<ParsedMessage> { public static async handleGetBackupStructure(
parsedMessage: ParsedMessage,
): Promise<ParsedMessage> {
if (!parsedMessage.metaInfo?.name) { if (!parsedMessage.metaInfo?.name) {
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing name in meta information.' }}; return {
operationCode: operationCodes.ERR,
metaInfo: { message: 'Missing name in meta information.' },
}
} }
const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name); const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name)
try { try {
const structure = await UserToUserOperations.buildDirectoryStructure(userBackupDir); const structure = await UserToUserOperations.buildDirectoryStructure(userBackupDir)
return { operationCode: operationCodes.OK, metaInfo: { structure }}; return { operationCode: operationCodes.OK, metaInfo: { structure } }
} catch (error: any) { } catch (error: any) {
console.error(`Error building backup structure: ${error.message}`); console.error(`Error building backup structure: ${error.message}`)
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` }}; return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` } }
} }
} }
private static async buildDirectoryStructure(directoryPath: string): Promise<any> { private static async buildDirectoryStructure(directoryPath: string): Promise<any> {
const structure: any = {}; const structure: any = {}
const files = await fs.readdir(directoryPath); const files = await fs.readdir(directoryPath)
for (const file of files) { for (const file of files) {
const filePath = path.join(directoryPath, file); const filePath = path.join(directoryPath, file)
const stats = await fs.stat(filePath); const stats = await fs.stat(filePath)
if (stats.isDirectory()) { if (stats.isDirectory()) {
structure[file] = await UserToUserOperations.buildDirectoryStructure(filePath); structure[file] = await UserToUserOperations.buildDirectoryStructure(filePath)
} else { } else {
structure[file] = path.relative(directoryPath, filePath); structure[file] = path.relative(directoryPath, filePath)
} }
} }
return structure; return structure
} }
public static async handleReqFileFromBackup(parsedMessage: ParsedMessage): Promise<ParsedMessage> { public static async handleReqFileFromBackup(
parsedMessage: ParsedMessage,
): Promise<ParsedMessage> {
if (!parsedMessage.metaInfo?.name || !parsedMessage.metaInfo?.relativeFilePath) { if (!parsedMessage.metaInfo?.name || !parsedMessage.metaInfo?.relativeFilePath) {
return { return {
operationCode: operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: 'Missing user name or file path in meta information.' }, metaInfo: { message: 'Missing user name or file path in meta information.' },
}; }
} }
const { name, relativeFilePath } = parsedMessage.metaInfo; const { name, relativeFilePath } = parsedMessage.metaInfo
const fullFilePath = path.join(__dirname, '..', '..', 'backups', name, relativeFilePath); const fullFilePath = path.join(__dirname, '..', '..', 'backups', name, relativeFilePath)
try { try {
const fileContent = await fs.readFile(fullFilePath); const fileContent = await fs.readFile(fullFilePath)
return { return {
operationCode: operationCodes.OK, operationCode: operationCodes.OK,
metaInfo: { relativeFilePath }, metaInfo: { relativeFilePath },
fileContent, fileContent,
}; }
} catch (error: any) { } catch (error: any) {
console.error(`Error reading file from backup: ${error.message}`); console.error(`Error reading file from backup: ${error.message}`)
return { return {
operationCode: operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: `Error reading file: ${error.message}` }, metaInfo: { message: `Error reading file: ${error.message}` },
}; }
} }
} }
public register(operationHandler: OperationHandler): void { public register(operationHandler: OperationHandler): void {
operationHandler.registerHandler(UserToUserOperations.operationCodes.SEND_ANNOUNCEMENT, UserToUserOperations.handleSendAnnouncement); operationHandler.registerHandler(
operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_USER_INFORMATION, UserToUserOperations.handleGetUserInformation); UserToUserOperations.operationCodes.SEND_ANNOUNCEMENT,
operationHandler.registerHandler(UserToUserOperations.operationCodes.BACKUP_FILE, UserToUserOperations.handleBackupFile); UserToUserOperations.handleSendAnnouncement,
operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_BACKUP, UserToUserOperations.handleClearBackup); )
operationHandler.registerHandler(UserToUserOperations.operationCodes.SHARE_FILE, UserToUserOperations.handleShareFile); operationHandler.registerHandler(
operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_DEPARTMENT, UserToUserOperations.handleClearDepartment); UserToUserOperations.operationCodes.GET_USER_INFORMATION,
operationHandler.registerHandler(UserToUserOperations.operationCodes.DEPARTMENT_FILE, UserToUserOperations.handleDepartmentFile); UserToUserOperations.handleGetUserInformation,
operationHandler.registerHandler(UserToUserOperations.operationCodes.IS_BACKUP_CREATED, UserToUserOperations.handleIsBackupCreated); )
operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_BACKUP_STRUCTURE, UserToUserOperations.handleGetBackupStructure); operationHandler.registerHandler(
operationHandler.registerHandler(UserToUserOperations.operationCodes.REQ_FILE_FROM_BACKUP, UserToUserOperations.handleReqFileFromBackup); UserToUserOperations.operationCodes.BACKUP_FILE,
UserToUserOperations.handleBackupFile,
)
operationHandler.registerHandler(
UserToUserOperations.operationCodes.CLEAR_BACKUP,
UserToUserOperations.handleClearBackup,
)
operationHandler.registerHandler(
UserToUserOperations.operationCodes.SHARE_FILE,
UserToUserOperations.handleShareFile,
)
operationHandler.registerHandler(
UserToUserOperations.operationCodes.CLEAR_DEPARTMENT,
UserToUserOperations.handleClearDepartment,
)
operationHandler.registerHandler(
UserToUserOperations.operationCodes.DEPARTMENT_FILE,
UserToUserOperations.handleDepartmentFile,
)
operationHandler.registerHandler(
UserToUserOperations.operationCodes.IS_BACKUP_CREATED,
UserToUserOperations.handleIsBackupCreated,
)
operationHandler.registerHandler(
UserToUserOperations.operationCodes.GET_BACKUP_STRUCTURE,
UserToUserOperations.handleGetBackupStructure,
)
operationHandler.registerHandler(
UserToUserOperations.operationCodes.REQ_FILE_FROM_BACKUP,
UserToUserOperations.handleReqFileFromBackup,
)
} }
} }
@@ -1,5 +1,5 @@
import { ParsedMessage } from '../message_handler'; import { ParsedMessage } from '../message_handler'
import { OperationHandler } from '../operations_base/operation_handler'; import { OperationHandler } from '../operations_base/operation_handler'
import { import {
constants, constants,
createCipheriv, createCipheriv,
@@ -7,44 +7,44 @@ import {
generateKeyPairSync, generateKeyPairSync,
privateEncrypt, privateEncrypt,
publicDecrypt, publicDecrypt,
randomBytes randomBytes,
} from "crypto"; } from 'crypto'
export abstract class SocketCommunicatorBase { export abstract class SocketCommunicatorBase {
protected readonly ip: string; protected readonly ip: string
protected readonly port: number; protected readonly port: number
protected readonly operationHandler: OperationHandler; protected readonly operationHandler: OperationHandler
protected handlerResult: ParsedMessage | null; protected handlerResult: ParsedMessage | null
protected chunkBuffers: { [messageId: string]: string[] }; protected chunkBuffers: { [messageId: string]: string[] }
protected privateKey: string | null; protected privateKey: string | null
protected publicKey: string | null; protected publicKey: string | null
protected aesKey: Buffer | null; protected aesKey: Buffer | null
protected aesIv: Buffer | null; protected aesIv: Buffer | null
protected readonly EOP = '<EOP>'; protected readonly EOP = '<EOP>'
protected readonly CHUNK_SIZE = 1024; protected readonly CHUNK_SIZE = 1024
private incompleteChunkBuffer: string = ''; private incompleteChunkBuffer: string = ''
protected constructor(ip: string, port: number, operationHandler: OperationHandler) { protected constructor(ip: string, port: number, operationHandler: OperationHandler) {
this.ip = ip; this.ip = ip
this.port = port; this.port = port
this.operationHandler = operationHandler this.operationHandler = operationHandler
this.handlerResult = null; this.handlerResult = null
this.chunkBuffers = {}; this.chunkBuffers = {}
this.privateKey = null; this.privateKey = null
this.publicKey = null; this.publicKey = null
this.aesKey = null; this.aesKey = null
this.aesIv = null; this.aesIv = null
} }
// Getter for the handler result // Getter for the handler result
getHandlerResult(): ParsedMessage | null { getHandlerResult(): ParsedMessage | null {
const result = this.handlerResult; const result = this.handlerResult
this.handlerResult = null; this.handlerResult = null
return result; return result
} }
protected generateKeyPair(): void { protected generateKeyPair(): void {
@@ -52,108 +52,111 @@ export abstract class SocketCommunicatorBase {
modulusLength: 2048, modulusLength: 2048,
publicKeyEncoding: { type: 'spki', format: 'pem' }, publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
}); })
this.privateKey = privateKey; this.privateKey = privateKey
this.publicKey = publicKey; this.publicKey = publicKey
} }
protected generateAesKey(): void { protected generateAesKey(): void {
this.aesKey = randomBytes(32); this.aesKey = randomBytes(32)
this.aesIv = randomBytes(16); this.aesIv = randomBytes(16)
} }
protected encryptWithAes(message: string): string { protected encryptWithAes(message: string): string {
if (!this.aesKey || !this.aesIv) { if (!this.aesKey || !this.aesIv) {
throw new Error('AES key or IV is not set.'); throw new Error('AES key or IV is not set.')
} }
const cipher = createCipheriv('aes-256-cbc', this.aesKey, this.aesIv); const cipher = createCipheriv('aes-256-cbc', this.aesKey, this.aesIv)
let encrypted = cipher.update(message, 'utf-8'); let encrypted = cipher.update(message, 'utf-8')
encrypted = Buffer.concat([encrypted, cipher.final()]); encrypted = Buffer.concat([encrypted, cipher.final()])
return encrypted.toString('base64'); return encrypted.toString('base64')
} }
protected decryptWithAes(encryptedMessage: string): string { protected decryptWithAes(encryptedMessage: string): string {
if (!this.aesKey || !this.aesIv) { if (!this.aesKey || !this.aesIv) {
throw new Error('AES key or IV is not set.'); throw new Error('AES key or IV is not set.')
} }
const decipher = createDecipheriv('aes-256-cbc', this.aesKey, this.aesIv); const decipher = createDecipheriv('aes-256-cbc', this.aesKey, this.aesIv)
let decrypted = decipher.update(Buffer.from(encryptedMessage, 'base64')); let decrypted = decipher.update(Buffer.from(encryptedMessage, 'base64'))
decrypted = Buffer.concat([decrypted, decipher.final()]); decrypted = Buffer.concat([decrypted, decipher.final()])
return decrypted.toString('utf-8'); return decrypted.toString('utf-8')
} }
protected decryptWithRsa(message: string): string { protected decryptWithRsa(message: string): string {
if (!this.publicKey) { if (!this.publicKey) {
throw new Error('Server public key not set.'); throw new Error('Server public key not set.')
} }
try { try {
const encryptedMessage = Buffer.from(message, 'base64'); const encryptedMessage = Buffer.from(message, 'base64')
const decrypted = publicDecrypt( const decrypted = publicDecrypt(
{ {
key: this.publicKey, key: this.publicKey,
padding: constants.RSA_PKCS1_PADDING, padding: constants.RSA_PKCS1_PADDING,
}, },
encryptedMessage encryptedMessage,
); )
return decrypted.toString('utf-8'); return decrypted.toString('utf-8')
} catch (error) { } catch (error) {
throw new Error('Failed to decrypt RSA message.'); throw new Error('Failed to decrypt RSA message.')
} }
} }
protected encryptWithRsa(message: string): string { protected encryptWithRsa(message: string): string {
if (!this.privateKey) throw new Error('Server private key not set.'); if (!this.privateKey) throw new Error('Server private key not set.')
return privateEncrypt( return privateEncrypt(
{ {
key: this.privateKey, key: this.privateKey,
padding: constants.RSA_PKCS1_PADDING, padding: constants.RSA_PKCS1_PADDING,
}, },
Buffer.from(message) Buffer.from(message),
).toString('base64'); ).toString('base64')
} }
async handleIncomingChunk(data: Buffer): Promise<void> { async handleIncomingChunk(data: Buffer): Promise<void> {
// Append incoming data to the incomplete buffer // Append incoming data to the incomplete buffer
this.incompleteChunkBuffer += data.toString(); this.incompleteChunkBuffer += data.toString()
// Split the buffer by <EOP> to separate complete and incomplete messages // Split the buffer by <EOP> to separate complete and incomplete messages
const messages = this.incompleteChunkBuffer.split(this.EOP); const messages = this.incompleteChunkBuffer.split(this.EOP)
// Save the last item back to the buffer if it's incomplete (no <EOP> at the end) // Save the last item back to the buffer if it's incomplete (no <EOP> at the end)
this.incompleteChunkBuffer = messages.pop() || ""; this.incompleteChunkBuffer = messages.pop() || ''
// Process each complete message in the split results // Process each complete message in the split results
for (const incomingMessage of messages) { for (const incomingMessage of messages) {
try { try {
const [headerJson, chunkContent] = incomingMessage.split('|'); const [headerJson, chunkContent] = incomingMessage.split('|')
const header = JSON.parse(headerJson); const header = JSON.parse(headerJson)
// Initialize an array for chunks if it's the first chunk for this messageId // Initialize an array for chunks if it's the first chunk for this messageId
if (!this.chunkBuffers[header.messageId]) { if (!this.chunkBuffers[header.messageId]) {
this.chunkBuffers[header.messageId] = []; this.chunkBuffers[header.messageId] = []
} }
// Store the chunk in the correct position based on sequenceNumber (1-based indexing) // Store the chunk in the correct position based on sequenceNumber (1-based indexing)
this.chunkBuffers[header.messageId][header.sequenceNumber - 1] = chunkContent; this.chunkBuffers[header.messageId][header.sequenceNumber - 1] = chunkContent
// Check if all chunks have been received // Check if all chunks have been received
if (this.chunkBuffers[header.messageId].filter(chunk => chunk !== undefined).length === header.totalChunks) { if (
this.chunkBuffers[header.messageId].filter((chunk) => chunk !== undefined).length ===
header.totalChunks
) {
// Join all chunks to form the full message // Join all chunks to form the full message
const fullMessage = this.chunkBuffers[header.messageId].join(''); const fullMessage = this.chunkBuffers[header.messageId].join('')
// Process the complete message // Process the complete message
await this.handleIncomingMessage(fullMessage); await this.handleIncomingMessage(fullMessage)
// Clear the chunk buffer for this messageId // Clear the chunk buffer for this messageId
delete this.chunkBuffers[header.messageId]; delete this.chunkBuffers[header.messageId]
} }
} catch (error: any) { } catch (error: any) {
console.error(`Error handling chunk: ${error.message}`); console.error(`Error handling chunk: ${error.message}`)
} }
} }
} }
abstract handleIncomingMessage(incomingMessage: string): Promise<void>; abstract handleIncomingMessage(incomingMessage: string): Promise<void>
abstract sendMessage(message: string): Promise<void>; abstract sendMessage(message: string): Promise<void>
} }
@@ -1,87 +1,90 @@
import { Socket } from 'net'; import { Socket } from 'net'
import { SocketCommunicatorBase } from './socket_communicator_base'; import { SocketCommunicatorBase } from './socket_communicator_base'
import { OperationHandler } from '../operations_base/operation_handler'; import { OperationHandler } from '../operations_base/operation_handler'
import { operationCodes } from '../operation_codes'; import { operationCodes } from '../operation_codes'
import {MessageHandler} from "../message_handler"; import { MessageHandler } from '../message_handler'
export class TcpClientCommunicator extends SocketCommunicatorBase { export class TcpClientCommunicator extends SocketCommunicatorBase {
private readonly socket: Socket; private readonly socket: Socket
private isAesKeySetFlag: boolean; private isAesKeySetFlag: boolean
constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) { constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) {
super(ip, port, operationHandler); super(ip, port, operationHandler)
this.socket = socket; this.socket = socket
this.aesKey = null; this.aesKey = null
this.aesIv = null; this.aesIv = null
this.isAesKeySetFlag = false; this.isAesKeySetFlag = false
this.chunkBuffers = {}; this.chunkBuffers = {}
} }
isAesKeySet(): boolean { isAesKeySet(): boolean {
return this.isAesKeySetFlag; return this.isAesKeySetFlag
} }
setServerPublicKey(publicKey: string): void { setServerPublicKey(publicKey: string): void {
this.publicKey = publicKey; this.publicKey = publicKey
} }
setAesKey(aesKey: string, aesIv: string): void { setAesKey(aesKey: string, aesIv: string): void {
this.aesKey = Buffer.from(aesKey, 'base64'); this.aesKey = Buffer.from(aesKey, 'base64')
this.aesIv = Buffer.from(aesIv, 'base64'); this.aesIv = Buffer.from(aesIv, 'base64')
} }
async handleIncomingMessage(incomingMessage: string): Promise<void> { async handleIncomingMessage(incomingMessage: string): Promise<void> {
let messageToProcess; let messageToProcess
if (this.aesKey && this.aesIv) { if (this.aesKey && this.aesIv) {
messageToProcess = this.decryptWithAes(incomingMessage); messageToProcess = this.decryptWithAes(incomingMessage)
} else if (this.publicKey) { } else if (this.publicKey) {
messageToProcess = this.decryptWithRsa(incomingMessage); messageToProcess = this.decryptWithRsa(incomingMessage)
} else { } else {
messageToProcess = Buffer.from(incomingMessage, 'base64').toString('utf-8'); messageToProcess = Buffer.from(incomingMessage, 'base64').toString('utf-8')
} }
const result = await this.operationHandler.handleOperation(messageToProcess); const result = await this.operationHandler.handleOperation(messageToProcess)
if (result.operationCode === operationCodes.SET_AES_KEY) { if (result.operationCode === operationCodes.SET_AES_KEY) {
this.isAesKeySetFlag = true; this.isAesKeySetFlag = true
this.setAesKey(result.metaInfo?.aesKey, result.metaInfo?.aesIv); this.setAesKey(result.metaInfo?.aesKey, result.metaInfo?.aesIv)
return; return
} }
if (result.operationCode === operationCodes.SET_PUBLIC_KEY) { if (result.operationCode === operationCodes.SET_PUBLIC_KEY) {
this.setServerPublicKey(result.metaInfo?.publicKey); this.setServerPublicKey(result.metaInfo?.publicKey)
return; return
} }
this.handlerResult = result; this.handlerResult = result
} }
async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise<void> { async sendMessage(
const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent); operationCode: string,
metaInfo?: { [key: string]: any },
fileContent?: Buffer,
): Promise<void> {
const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent)
const outgoingMessage = this.encryptWithAes(message); const outgoingMessage = this.encryptWithAes(message)
// Calculate optimal chunk size based on network latency // Calculate optimal chunk size based on network latency
const totalChunks = Math.ceil(outgoingMessage.length / this.CHUNK_SIZE); const totalChunks = Math.ceil(outgoingMessage.length / this.CHUNK_SIZE)
const messageId = Date.now().toString(); const messageId = Date.now().toString()
// Send each chunk with a delay between them // Send each chunk with a delay between them
for (let i = 0; i < totalChunks; i++) { for (let i = 0; i < totalChunks; i++) {
const chunk = outgoingMessage.slice(i * this.CHUNK_SIZE, (i + 1) * this.CHUNK_SIZE); const chunk = outgoingMessage.slice(i * this.CHUNK_SIZE, (i + 1) * this.CHUNK_SIZE)
const chunkHeader = JSON.stringify({ const chunkHeader = JSON.stringify({
messageId, messageId,
sequenceNumber: i + 1, sequenceNumber: i + 1,
totalChunks, totalChunks,
}); })
const chunkWithHeader = `${chunkHeader}|${chunk}${this.EOP}`; const chunkWithHeader = `${chunkHeader}|${chunk}${this.EOP}`
if (!this.socket.write(chunkWithHeader)) { if (!this.socket.write(chunkWithHeader)) {
// Wait for the 'drain' event before writing the next chunk // Wait for the 'drain' event before writing the next chunk
await new Promise((resolve) => this.socket.once('drain', resolve)); await new Promise((resolve) => this.socket.once('drain', resolve))
} }
} }
} }
} }
@@ -1,73 +1,77 @@
import { Socket } from 'net'; import { Socket } from 'net'
import { MessageHandler } from '../message_handler'; import { MessageHandler } from '../message_handler'
import { SocketCommunicatorBase } from './socket_communicator_base'; import { SocketCommunicatorBase } from './socket_communicator_base'
import { OperationHandler } from '../operations_base/operation_handler'; import { OperationHandler } from '../operations_base/operation_handler'
import {operationCodes} from "../operation_codes"; import { operationCodes } from '../operation_codes'
export class TcpServerCommunicator extends SocketCommunicatorBase { export class TcpServerCommunicator extends SocketCommunicatorBase {
private readonly socket: Socket; private readonly socket: Socket
constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) { constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) {
super(ip, port, operationHandler); super(ip, port, operationHandler)
this.socket = socket; this.socket = socket
} }
async sendPublicKey(): Promise<void> { async sendPublicKey(): Promise<void> {
this.generateKeyPair(); this.generateKeyPair()
if (!this.publicKey || !this.privateKey) { if (!this.publicKey || !this.privateKey) {
throw new Error('RSA key pair is not available. Please generate RSA key pair.'); throw new Error('RSA key pair is not available. Please generate RSA key pair.')
} }
await this.sendMessage(operationCodes.SET_PUBLIC_KEY, { publicKey: this.publicKey }); await this.sendMessage(operationCodes.SET_PUBLIC_KEY, { publicKey: this.publicKey })
} }
async sendAesKey(): Promise<void> { async sendAesKey(): Promise<void> {
this.generateAesKey(); this.generateAesKey()
if (!this.aesKey || !this.aesIv) { if (!this.aesKey || !this.aesIv) {
throw new Error('AES key or IV is not available. Please generate AES key.'); throw new Error('AES key or IV is not available. Please generate AES key.')
} }
const aesKeyBase64 = this.aesKey.toString('base64'); const aesKeyBase64 = this.aesKey.toString('base64')
const aesIvBase64 = this.aesIv.toString('base64'); const aesIvBase64 = this.aesIv.toString('base64')
await this.sendMessage(operationCodes.SET_AES_KEY, { aesKey: aesKeyBase64, aesIv: aesIvBase64 }); await this.sendMessage(operationCodes.SET_AES_KEY, { aesKey: aesKeyBase64, aesIv: aesIvBase64 })
} }
async handleIncomingMessage(incomingMessage: string): Promise<void> { async handleIncomingMessage(incomingMessage: string): Promise<void> {
const messageToProcess = this.decryptWithAes(incomingMessage); const messageToProcess = this.decryptWithAes(incomingMessage)
this.handlerResult = await this.operationHandler.handleOperation(messageToProcess); this.handlerResult = await this.operationHandler.handleOperation(messageToProcess)
} }
async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise<void> { async sendMessage(
const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent); operationCode: string,
metaInfo?: { [key: string]: any },
fileContent?: Buffer,
): Promise<void> {
const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent)
let outgoingMessage: string; let outgoingMessage: string
switch (operationCode) { switch (operationCode) {
case operationCodes.SET_PUBLIC_KEY: case operationCodes.SET_PUBLIC_KEY:
outgoingMessage = Buffer.from(message, 'utf-8').toString('base64'); outgoingMessage = Buffer.from(message, 'utf-8').toString('base64')
break; break
case operationCodes.SET_AES_KEY: case operationCodes.SET_AES_KEY:
outgoingMessage = this.encryptWithRsa(message); outgoingMessage = this.encryptWithRsa(message)
break; break
default: default:
outgoingMessage = this.encryptWithAes(message); outgoingMessage = this.encryptWithAes(message)
} }
const totalChunks = Math.ceil(outgoingMessage.length / this.CHUNK_SIZE); const totalChunks = Math.ceil(outgoingMessage.length / this.CHUNK_SIZE)
const messageId = Date.now().toString(); const messageId = Date.now().toString()
// Send each chunk with a delay between them // Send each chunk with a delay between them
for (let i = 0; i < totalChunks; i++) { for (let i = 0; i < totalChunks; i++) {
const chunk = outgoingMessage.slice(i * this.CHUNK_SIZE, (i + 1) * this.CHUNK_SIZE); const chunk = outgoingMessage.slice(i * this.CHUNK_SIZE, (i + 1) * this.CHUNK_SIZE)
const chunkHeader = JSON.stringify({ const chunkHeader = JSON.stringify({
messageId, messageId,
sequenceNumber: i + 1, sequenceNumber: i + 1,
totalChunks, totalChunks,
}); })
const chunkWithHeader = `${chunkHeader}|${chunk}${this.EOP}`; const chunkWithHeader = `${chunkHeader}|${chunk}${this.EOP}`
if (!this.socket.write(chunkWithHeader)) { if (!this.socket.write(chunkWithHeader)) {
// Wait for the 'drain' event before writing the next chunk // Wait for the 'drain' event before writing the next chunk
await new Promise((resolve) => this.socket.once('drain', resolve)); await new Promise((resolve) => this.socket.once('drain', resolve))
} }
} }
} }
@@ -1,33 +1,37 @@
import { Socket as UdpSocket } from 'dgram'; import { Socket as UdpSocket } from 'dgram'
import { MessageHandler } from '../message_handler'; import { MessageHandler } from '../message_handler'
import { SocketCommunicatorBase } from './socket_communicator_base'; import { SocketCommunicatorBase } from './socket_communicator_base'
import {OperationHandler} from "../operations_base/operation_handler"; import { OperationHandler } from '../operations_base/operation_handler'
export class UdpSocketCommunicator extends SocketCommunicatorBase { export class UdpSocketCommunicator extends SocketCommunicatorBase {
private readonly socket: UdpSocket; private readonly socket: UdpSocket
constructor(socket: UdpSocket, ip: string, port: number, operationHandler: OperationHandler) { constructor(socket: UdpSocket, ip: string, port: number, operationHandler: OperationHandler) {
super(ip, port, operationHandler); super(ip, port, operationHandler)
this.socket = socket; this.socket = socket
} }
// Send plain message over UDP (metaInfo as JSON and fileContent as Buffer) // Send plain message over UDP (metaInfo as JSON and fileContent as Buffer)
async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise<void> { async sendMessage(
const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent); operationCode: string,
metaInfo?: { [key: string]: any },
fileContent?: Buffer,
): Promise<void> {
const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.socket.send(message, this.port, this.ip, (err: any) => { this.socket.send(message, this.port, this.ip, (err: any) => {
if (err) { if (err) {
console.error('Error sending UDP message:', err); console.error('Error sending UDP message:', err)
return reject(err); return reject(err)
} }
console.log(`Plain message sent to ${this.ip}:${this.port} (UDP)`); console.log(`Plain message sent to ${this.ip}:${this.port} (UDP)`)
resolve(); resolve()
}); })
}); })
} }
// Handle incoming message (no decryption needed for UDP) // Handle incoming message (no decryption needed for UDP)
async handleIncomingMessage(incomingMessage: string): Promise<void> { async handleIncomingMessage(incomingMessage: string): Promise<void> {
this.handlerResult = await this.operationHandler.handleOperation(incomingMessage); this.handlerResult = await this.operationHandler.handleOperation(incomingMessage)
} }
} }
+64 -55
View File
@@ -1,111 +1,120 @@
import net, { Socket } from 'net'; import net, { Socket } from 'net'
import { TcpClientCommunicator } from '../socket_communicator/tcp_client_communicator'; import { TcpClientCommunicator } from '../socket_communicator/tcp_client_communicator'
import { OperationHandler } from '../operations_base/operation_handler'; import { OperationHandler } from '../operations_base/operation_handler'
import { operationCodes } from "../operation_codes"; import { operationCodes } from '../operation_codes'
import { GeneralOperations } from "../operations_custom/general_operations"; import { GeneralOperations } from '../operations_custom/general_operations'
import { UserToUserOperations } from "../operations_custom/user_to_user_operations"; import { UserToUserOperations } from '../operations_custom/user_to_user_operations'
import { ParsedMessage } from "../message_handler"; import { ParsedMessage } from '../message_handler'
export class TcpClient { export class TcpClient {
private readonly tcp_port: number; private readonly tcp_port: number
private socket: Socket | null; private socket: Socket | null
private communicator: TcpClientCommunicator | null; private communicator: TcpClientCommunicator | null
private readonly operationHandler: OperationHandler; private readonly operationHandler: OperationHandler
private lastResult: ParsedMessage | null; private lastResult: ParsedMessage | null
constructor(tcp_port: number) { constructor(tcp_port: number) {
this.tcp_port = tcp_port; this.tcp_port = tcp_port
this.socket = null; this.socket = null
this.communicator = null; this.communicator = null
this.operationHandler = OperationHandler.getInstance(); this.operationHandler = OperationHandler.getInstance()
this.lastResult = null; this.lastResult = null
this.operationHandler.loadPlugin(new GeneralOperations()); this.operationHandler.loadPlugin(new GeneralOperations())
this.operationHandler.loadPlugin(new UserToUserOperations()); this.operationHandler.loadPlugin(new UserToUserOperations())
} }
// Unified logging function // Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void { private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[TcpClient]'; const prefix = '[TcpClient]'
if (level === 'error') { if (level === 'error') {
console.error(`${prefix} ${message}`); console.error(`${prefix} ${message}`)
} else { } else {
console.log(`${prefix} ${message}`); console.log(`${prefix} ${message}`)
} }
} }
// Open a TCP socket connection // Open a TCP socket connection
openSocket(ip: string): void { openSocket(ip: string): void {
this.socket = new net.Socket(); this.socket = new net.Socket()
this.socket.connect(this.tcp_port, ip, () => { this.socket.connect(this.tcp_port, ip, () => {
this.log(`Connected to server at ${ip}:${this.tcp_port}`); this.log(`Connected to server at ${ip}:${this.tcp_port}`)
this.communicator = new TcpClientCommunicator(this.socket as Socket, ip, this.tcp_port, this.operationHandler); this.communicator = new TcpClientCommunicator(
}); this.socket as Socket,
ip,
this.tcp_port,
this.operationHandler,
)
})
this.socket.on('error', (err) => { this.socket.on('error', (err) => {
this.log(`Connection error to server at ${ip}:${this.tcp_port}: ${err.message}`, 'error'); this.log(`Connection error to server at ${ip}:${this.tcp_port}: ${err.message}`, 'error')
}); })
this.socket.on('data', async (data: Buffer) => { this.socket.on('data', async (data: Buffer) => {
if (this.communicator) { if (this.communicator) {
await this.communicator.handleIncomingChunk(data); await this.communicator.handleIncomingChunk(data)
this.lastResult = this.communicator.getHandlerResult(); this.lastResult = this.communicator.getHandlerResult()
this.log(`Data received from ${ip}:${this.tcp_port}`); this.log(`Data received from ${ip}:${this.tcp_port}`)
} }
}); })
this.socket.on('close', () => { this.socket.on('close', () => {
this.log(`Connection closed: ${ip}:${this.tcp_port}`); this.log(`Connection closed: ${ip}:${this.tcp_port}`)
this.lastResult = null; this.lastResult = null
}); })
} }
// Close the socket connection // Close the socket connection
closeSocket(): void { closeSocket(): void {
if (this.socket) { if (this.socket) {
this.socket.end(); this.socket.end()
this.socket = null; this.socket = null
this.communicator = null; this.communicator = null
this.lastResult = null; this.lastResult = null
this.log('Client socket connection closed.'); this.log('Client socket connection closed.')
} }
} }
// Send a message with operationCode, metaInfo, and fileContent in chunks // Send a message with operationCode, metaInfo, and fileContent in chunks
async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise<boolean> { async sendMessage(
operationCode: string,
metaInfo?: { [key: string]: any },
fileContent?: Buffer,
): Promise<boolean> {
if (!this.communicator || !this.isAesKeySet()) { if (!this.communicator || !this.isAesKeySet()) {
this.log('Communicator not initialized or AES key not set.', 'error'); this.log('Communicator not initialized or AES key not set.', 'error')
return false; return false
} }
this.log(`Sending message with operationCode: ${operationCode}`); this.log(`Sending message with operationCode: ${operationCode}`)
await this.communicator.sendMessage(operationCode, metaInfo, fileContent); await this.communicator.sendMessage(operationCode, metaInfo, fileContent)
return true; return true
} }
// Check if AES key is set // Check if AES key is set
isAesKeySet(): boolean { isAesKeySet(): boolean {
if (!this.communicator) return false; if (!this.communicator) return false
return this.communicator?.isAesKeySet(); return this.communicator?.isAesKeySet()
} }
// Check if the message is received (based on if lastResult is available) // Check if the message is received (based on if lastResult is available)
isMessageReceived(): boolean { isMessageReceived(): boolean {
return this.lastResult !== null; return this.lastResult !== null
} }
// Get the last result (and clear it after returning) // Get the last result (and clear it after returning)
getLastResult(): ParsedMessage | null { getLastResult(): ParsedMessage | null {
const result = this.lastResult; const result = this.lastResult
this.lastResult = null; this.lastResult = null
return result; return result
} }
// Check if the socket is still connected // Check if the socket is still connected
isSocketConnected(): boolean { isSocketConnected(): boolean {
const connected = this.socket !== null && !this.socket.destroyed; const connected = this.socket !== null && !this.socket.destroyed
this.log(`Socket connected: ${connected}`); this.log(`Socket connected: ${connected}`)
return connected; return connected
} }
} }
+69 -66
View File
@@ -1,118 +1,121 @@
import net, { Socket } from 'net'; import net, { Socket } from 'net'
import path from 'path'; import path from 'path'
import dotenv from 'dotenv'; import dotenv from 'dotenv'
import { ConnectionManager } from "../connection_manager"; import { ConnectionManager } from '../connection_manager'
import { TcpServerCommunicator } from "../socket_communicator/tcp_server_communicator"; import { TcpServerCommunicator } from '../socket_communicator/tcp_server_communicator'
import { GeneralOperations } from "../operations_custom/general_operations"; import { GeneralOperations } from '../operations_custom/general_operations'
import { OperationHandler } from "../operations_base/operation_handler"; import { OperationHandler } from '../operations_base/operation_handler'
import { UserToUserOperations } from "../operations_custom/user_to_user_operations"; import { UserToUserOperations } from '../operations_custom/user_to_user_operations'
dotenv.config({ path: path.resolve(__dirname, './config/.env') }); dotenv.config({ path: path.resolve(__dirname, './config/.env') })
export class TcpServer { export class TcpServer {
private readonly connectionManager: ConnectionManager; private readonly connectionManager: ConnectionManager
private readonly operationHandler: OperationHandler; private readonly operationHandler: OperationHandler
private readonly port: number; private readonly port: number
private readonly host: string; private readonly host: string
private clientQueues: Map<string, Promise<void>> = new Map(); private clientQueues: Map<string, Promise<void>> = new Map()
constructor(host: string, port: number) { constructor(host: string, port: number) {
this.connectionManager = new ConnectionManager(); this.connectionManager = new ConnectionManager()
this.operationHandler = OperationHandler.getInstance(); this.operationHandler = OperationHandler.getInstance()
this.host = host; this.host = host
this.port = port; this.port = port
this.operationHandler.loadPlugin(new GeneralOperations()); this.operationHandler.loadPlugin(new GeneralOperations())
this.operationHandler.loadPlugin(new UserToUserOperations()); this.operationHandler.loadPlugin(new UserToUserOperations())
} }
private log(message: string, level: 'log' | 'error' = 'log'): void { private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[TcpServer]'; const prefix = '[TcpServer]'
console[level === 'error' ? 'error' : 'log'](`${prefix} ${message}`); console[level === 'error' ? 'error' : 'log'](`${prefix} ${message}`)
} }
public start(): void { public start(): void {
const tcpServer = net.createServer(); const tcpServer = net.createServer()
tcpServer.on('connection', (socket: Socket) => { tcpServer.on('connection', (socket: Socket) => {
const ip = socket.remoteAddress || 'unknown'; const ip = socket.remoteAddress || 'unknown'
const port = socket.remotePort || 0; const port = socket.remotePort || 0
const clientId = `${ip}:${port}`; const clientId = `${ip}:${port}`
this.log(`Client connected: ${clientId}`); this.log(`Client connected: ${clientId}`)
const tcpCommunicator = new TcpServerCommunicator(socket, ip, port, this.operationHandler); const tcpCommunicator = new TcpServerCommunicator(socket, ip, port, this.operationHandler)
this.connectionManager.addConnection(ip, port, tcpCommunicator); this.connectionManager.addConnection(ip, port, tcpCommunicator)
tcpCommunicator.sendPublicKey() tcpCommunicator
.sendPublicKey()
.then(() => tcpCommunicator.sendAesKey()) .then(() => tcpCommunicator.sendAesKey())
.catch(err => { .catch((err) => {
this.log(`Error during key exchange with client ${clientId}: ${err}`, 'error'); this.log(`Error during key exchange with client ${clientId}: ${err}`, 'error')
socket.end(); socket.end()
}); })
this.clientQueues.set(clientId, Promise.resolve()); this.clientQueues.set(clientId, Promise.resolve())
socket.on('data', (data: Buffer) => { socket.on('data', (data: Buffer) => {
this.queueClientDataProcessing(data, ip, port); this.queueClientDataProcessing(data, ip, port)
}); })
socket.on('end', () => { socket.on('end', () => {
this.log(`Client disconnected: ${clientId}`); this.log(`Client disconnected: ${clientId}`)
this.connectionManager.removeCommunicator(ip, port); this.connectionManager.removeCommunicator(ip, port)
this.clientQueues.delete(clientId); this.clientQueues.delete(clientId)
}); })
socket.on('error', (err: Error) => { socket.on('error', (err: Error) => {
this.log(`Error from client ${clientId}: ${err.message}`, 'error'); this.log(`Error from client ${clientId}: ${err.message}`, 'error')
this.connectionManager.removeCommunicator(ip, port); this.connectionManager.removeCommunicator(ip, port)
this.clientQueues.delete(clientId); this.clientQueues.delete(clientId)
}); })
}); })
tcpServer.on('error', (err: Error) => { tcpServer.on('error', (err: Error) => {
this.log(`TCP server error: ${err.message}`, 'error'); this.log(`TCP server error: ${err.message}`, 'error')
}); })
tcpServer.listen(this.port, this.host, () => { tcpServer.listen(this.port, this.host, () => {
this.log(`TCP server listening on ${this.host}:${this.port}`); this.log(`TCP server listening on ${this.host}:${this.port}`)
}); })
} }
private queueClientDataProcessing(data: Buffer, ip: string, port: number): void { private queueClientDataProcessing(data: Buffer, ip: string, port: number): void {
const clientId = `${ip}:${port}`; const clientId = `${ip}:${port}`
const clientQueue = this.clientQueues.get(clientId) || Promise.resolve(); const clientQueue = this.clientQueues.get(clientId) || Promise.resolve()
this.clientQueues.set( this.clientQueues.set(
clientId, clientId,
clientQueue.then(() => this.handleData(data, ip, port)).catch(error => { clientQueue
this.log(`Error handling data for ${clientId}: ${error}`, 'error'); .then(() => this.handleData(data, ip, port))
}) .catch((error) => {
); this.log(`Error handling data for ${clientId}: ${error}`, 'error')
}),
)
} }
private async handleData(data: Buffer, ip: string, port: number): Promise<void> { private async handleData(data: Buffer, ip: string, port: number): Promise<void> {
const clientId = `${ip}:${port}`; const clientId = `${ip}:${port}`
const communicator = this.connectionManager.getCommunicator(ip, port) as TcpServerCommunicator; const communicator = this.connectionManager.getCommunicator(ip, port) as TcpServerCommunicator
if (!communicator) { if (!communicator) {
this.log(`No communicator found for ${clientId}`, 'error'); this.log(`No communicator found for ${clientId}`, 'error')
return; return
} }
await communicator.handleIncomingChunk(data); await communicator.handleIncomingChunk(data)
// Check if message is complete before fetching result // Check if message is complete before fetching result
const handlerResult = communicator.getHandlerResult(); const handlerResult = communicator.getHandlerResult()
if (handlerResult) { if (handlerResult) {
try { try {
await communicator.sendMessage( await communicator.sendMessage(
handlerResult.operationCode, handlerResult.operationCode,
handlerResult.metaInfo, handlerResult.metaInfo,
handlerResult.fileContent handlerResult.fileContent,
); )
this.log(`Response sent to ${clientId}`); this.log(`Response sent to ${clientId}`)
} catch (err) { } catch (err) {
this.log(`Failed to send response to ${clientId}: ${err}`, 'error'); this.log(`Failed to send response to ${clientId}: ${err}`, 'error')
} }
} }
} }
+68 -68
View File
@@ -1,161 +1,161 @@
import dgram from 'dgram'; import dgram from 'dgram'
import ping from 'ping'; import ping from 'ping'
import { OperationHandler } from '../operations_base/operation_handler'; import { OperationHandler } from '../operations_base/operation_handler'
import { MessageHandler } from '../message_handler'; import { MessageHandler } from '../message_handler'
import { GeneralOperations } from "../operations_custom/general_operations"; import { GeneralOperations } from '../operations_custom/general_operations'
import { operationCodes } from "../operation_codes"; import { operationCodes } from '../operation_codes'
import os from 'os'; import os from 'os'
export class UdpClient { export class UdpClient {
private udpSocket: dgram.Socket; private udpSocket: dgram.Socket
private readonly port: number; private readonly port: number
private operationHandler: OperationHandler; private operationHandler: OperationHandler
constructor(port: number) { constructor(port: number) {
this.port = port; this.port = port
this.udpSocket = dgram.createSocket('udp4'); this.udpSocket = dgram.createSocket('udp4')
this.operationHandler = OperationHandler.getInstance(); this.operationHandler = OperationHandler.getInstance()
this.operationHandler.loadPlugin(new GeneralOperations()); this.operationHandler.loadPlugin(new GeneralOperations())
} }
// Unified logging function // Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void { private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[UdpClient]'; const prefix = '[UdpClient]'
if (level === 'error') { if (level === 'error') {
console.error(`${prefix} ${message}`); console.error(`${prefix} ${message}`)
} else { } else {
console.log(`${prefix} ${message}`); console.log(`${prefix} ${message}`)
} }
} }
// Send heartbeat and get clients that respond with ALIVE, excluding the host machine's IP addresses // Send heartbeat and get clients that respond with ALIVE, excluding the host machine's IP addresses
async getTargetClients(heartbeatCode: string): Promise<string[]> { async getTargetClients(heartbeatCode: string): Promise<string[]> {
const subnet = this.getSubnet(); const subnet = this.getSubnet()
const ipRange = this.getIPRange(subnet); const ipRange = this.getIPRange(subnet)
// Get local machine's IP addresses to exclude // Get local machine's IP addresses to exclude
const localIPs = this.getLocalIPs(); const localIPs = this.getLocalIPs()
this.log(`Local IPs to exclude from scan: ${localIPs.join(', ')}`); this.log(`Local IPs to exclude from scan: ${localIPs.join(', ')}`)
// First, filter active IPs that respond to ping // First, filter active IPs that respond to ping
const activeIps = await this.filterActiveIps(ipRange); const activeIps = await this.filterActiveIps(ipRange)
this.log(`Active IPs in subnet ${subnet}: ${activeIps.join(', ')}`); this.log(`Active IPs in subnet ${subnet}: ${activeIps.join(', ')}`)
// Send heartbeat to each active IP and keep only those that respond with ALIVE // Send heartbeat to each active IP and keep only those that respond with ALIVE
const aliveClients: string[] = []; const aliveClients: string[] = []
for (const ip of activeIps) { for (const ip of activeIps) {
if (!localIPs.includes(ip)) { if (!localIPs.includes(ip)) {
const result = await this.sendHeartbeat(ip, heartbeatCode); const result = await this.sendHeartbeat(ip, heartbeatCode)
if (result.found) { if (result.found) {
aliveClients.push(ip); aliveClients.push(ip)
} }
} }
} }
this.log(`Alive clients (excluding local machine): ${aliveClients.join(', ')}`); this.log(`Alive clients (excluding local machine): ${aliveClients.join(', ')}`)
return aliveClients; return aliveClients
} }
// Get local IP addresses of the host machine (excluding loopback) // Get local IP addresses of the host machine (excluding loopback)
private getLocalIPs(): string[] { private getLocalIPs(): string[] {
const interfaces = os.networkInterfaces(); const interfaces = os.networkInterfaces()
const localIPs: string[] = []; const localIPs: string[] = []
Object.values(interfaces).forEach((iface) => { Object.values(interfaces).forEach((iface) => {
iface?.forEach((address) => { iface?.forEach((address) => {
if (address.family === 'IPv4' && !address.internal) { if (address.family === 'IPv4' && !address.internal) {
localIPs.push(address.address); localIPs.push(address.address)
} }
}); })
}); })
return localIPs; return localIPs
} }
// Send heartbeat to an IP // Send heartbeat to an IP
private async sendHeartbeat(ip: string, heartbeatCode: string): Promise<{ found: boolean }> { private async sendHeartbeat(ip: string, heartbeatCode: string): Promise<{ found: boolean }> {
return new Promise((resolve) => { return new Promise((resolve) => {
const heartbeatMessage = MessageHandler.formatMessage(heartbeatCode); const heartbeatMessage = MessageHandler.formatMessage(heartbeatCode)
this.log(`Sending heartbeat to ${ip}`); this.log(`Sending heartbeat to ${ip}`)
this.udpSocket.send(heartbeatMessage, this.port, ip, (err) => { this.udpSocket.send(heartbeatMessage, this.port, ip, (err) => {
if (err) { if (err) {
this.log(`Failed to send heartbeat to ${ip}: ${err.message}`, 'error'); this.log(`Failed to send heartbeat to ${ip}: ${err.message}`, 'error')
resolve({ found: false }); resolve({ found: false })
} else { } else {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
this.dropConnection(ip); this.dropConnection(ip)
resolve({ found: false }); resolve({ found: false })
}, 1500); }, 1500)
this.udpSocket.once('message', (msg, rinfo) => { this.udpSocket.once('message', (msg, rinfo) => {
if (rinfo.address === ip) { if (rinfo.address === ip) {
clearTimeout(timeout); clearTimeout(timeout)
const parsedMessage = MessageHandler.parseMessage(msg.toString()); const parsedMessage = MessageHandler.parseMessage(msg.toString())
if (parsedMessage?.operationCode === operationCodes.ALIVE) { if (parsedMessage?.operationCode === operationCodes.ALIVE) {
this.log(`Received ALIVE response from ${ip}`); this.log(`Received ALIVE response from ${ip}`)
resolve({ found: true }); resolve({ found: true })
} else { } else {
this.log(`Unexpected response from ${ip}`); this.log(`Unexpected response from ${ip}`)
resolve({ found: false }); resolve({ found: false })
} }
} }
}); })
} }
}); })
}); })
} }
// Drop connection for a specific IP // Drop connection for a specific IP
private dropConnection(ip: string): void { private dropConnection(ip: string): void {
try { try {
this.udpSocket.removeAllListeners('message'); this.udpSocket.removeAllListeners('message')
this.log(`Dropped connection listeners for ${ip}`); this.log(`Dropped connection listeners for ${ip}`)
} catch (err: any) { } catch (err: any) {
this.log(`Error dropping connection to ${ip}: ${err.message}`, 'error'); this.log(`Error dropping connection to ${ip}: ${err.message}`, 'error')
} }
} }
// Get the subnet (e.g., 192.168.1) // Get the subnet (e.g., 192.168.1)
private getSubnet(): string { private getSubnet(): string {
const interfaces = os.networkInterfaces(); const interfaces = os.networkInterfaces()
for (const iface of Object.values(interfaces)) { for (const iface of Object.values(interfaces)) {
for (const address of iface || []) { for (const address of iface || []) {
if (address.family === 'IPv4' && !address.internal) { if (address.family === 'IPv4' && !address.internal) {
const subnet = address.address.split('.').slice(0, 3).join('.'); const subnet = address.address.split('.').slice(0, 3).join('.')
this.log(`Detected subnet: ${subnet}`); this.log(`Detected subnet: ${subnet}`)
return subnet; return subnet
} }
} }
} }
return ''; return ''
} }
// Get IP range (assuming /24 subnet) // Get IP range (assuming /24 subnet)
private getIPRange(subnet: string): string[] { private getIPRange(subnet: string): string[] {
const ipRange = []; const ipRange = []
for (let i = 1; i < 255; i++) { for (let i = 1; i < 255; i++) {
ipRange.push(`${subnet}.${i}`); ipRange.push(`${subnet}.${i}`)
} }
this.log(`Generated IP range for subnet ${subnet}`); this.log(`Generated IP range for subnet ${subnet}`)
return ipRange; return ipRange
} }
// Filter only active IPs by pinging each IP in the range // Filter only active IPs by pinging each IP in the range
private async filterActiveIps(ipRange: string[]): Promise<string[]> { private async filterActiveIps(ipRange: string[]): Promise<string[]> {
const activeIps: string[] = []; const activeIps: string[] = []
const pingPromises = ipRange.map(ip => ping.promise.probe(ip, { timeout: 1 })); const pingPromises = ipRange.map((ip) => ping.promise.probe(ip, { timeout: 1 }))
const pingResults = await Promise.all(pingPromises); const pingResults = await Promise.all(pingPromises)
for (const result of pingResults) { for (const result of pingResults) {
if (result.alive) { if (result.alive) {
activeIps.push(result.host); activeIps.push(result.host)
} }
} }
this.log(`Active IPs after pinging: ${activeIps.join(', ')}`); this.log(`Active IPs after pinging: ${activeIps.join(', ')}`)
return activeIps; return activeIps
} }
} }
+35 -32
View File
@@ -1,77 +1,80 @@
import dgram, { RemoteInfo } from 'dgram'; import dgram, { RemoteInfo } from 'dgram'
import { UdpSocketCommunicator } from "../socket_communicator/udp_socket_communicator"; import { UdpSocketCommunicator } from '../socket_communicator/udp_socket_communicator'
import { OperationHandler } from "../operations_base/operation_handler"; import { OperationHandler } from '../operations_base/operation_handler'
import { GeneralOperations } from "../operations_custom/general_operations"; import { GeneralOperations } from '../operations_custom/general_operations'
export class UdpServer { export class UdpServer {
private readonly udpServer: dgram.Socket; private readonly udpServer: dgram.Socket
private readonly operationHandler: OperationHandler; private readonly operationHandler: OperationHandler
private readonly port: number; private readonly port: number
private readonly host: string; private readonly host: string
constructor(host: string, port: number) { constructor(host: string, port: number) {
this.udpServer = dgram.createSocket('udp4'); this.udpServer = dgram.createSocket('udp4')
this.operationHandler = OperationHandler.getInstance(); this.operationHandler = OperationHandler.getInstance()
this.host = host; this.host = host
this.port = port; this.port = port
// Load only the GeneralOperations into the operation handler // Load only the GeneralOperations into the operation handler
this.operationHandler.loadPlugin(new GeneralOperations()); this.operationHandler.loadPlugin(new GeneralOperations())
} }
// Unified logging function // Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void { private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[UdpServer]'; const prefix = '[UdpServer]'
if (level === 'error') { if (level === 'error') {
console.error(`${prefix} ${message}`); console.error(`${prefix} ${message}`)
} else { } else {
console.log(`${prefix} ${message}`); console.log(`${prefix} ${message}`)
} }
} }
// Start the UDP server // Start the UDP server
public start(): void { public start(): void {
this.udpServer.on('message', this.handleUdpMessages.bind(this)); this.udpServer.on('message', this.handleUdpMessages.bind(this))
this.udpServer.on('error', this.handleError.bind(this)); this.udpServer.on('error', this.handleError.bind(this))
this.udpServer.on('listening', this.handleListening.bind(this)); this.udpServer.on('listening', this.handleListening.bind(this))
// Bind the server to the UDP port and host // Bind the server to the UDP port and host
this.udpServer.bind(this.port, this.host); this.udpServer.bind(this.port, this.host)
} }
// Handle incoming UDP messages // Handle incoming UDP messages
private async handleUdpMessages(msg: Buffer, rinfo: RemoteInfo): Promise<void> { private async handleUdpMessages(msg: Buffer, rinfo: RemoteInfo): Promise<void> {
const ip = rinfo.address; const ip = rinfo.address
const port = rinfo.port; const port = rinfo.port
this.log(`Received message from ${ip}:${port}`); this.log(`Received message from ${ip}:${port}`)
// Create a temporary communicator for the incoming message // Create a temporary communicator for the incoming message
const communicator = new UdpSocketCommunicator(this.udpServer, ip, port, this.operationHandler); const communicator = new UdpSocketCommunicator(this.udpServer, ip, port, this.operationHandler)
// Process the incoming message using the communicator // Process the incoming message using the communicator
await communicator.handleIncomingMessage(msg.toString()); await communicator.handleIncomingMessage(msg.toString())
const communicatorResult = communicator.getHandlerResult(); const communicatorResult = communicator.getHandlerResult()
if (communicatorResult) { if (communicatorResult) {
// Send response back to the client using the temporary communicator // Send response back to the client using the temporary communicator
try { try {
await communicator.sendMessage(communicatorResult.operationCode, communicatorResult.metaInfo); await communicator.sendMessage(
this.log(`Sent response to ${ip}:${port}`); communicatorResult.operationCode,
communicatorResult.metaInfo,
)
this.log(`Sent response to ${ip}:${port}`)
} catch (error: any) { } catch (error: any) {
this.log(`Error sending response to ${ip}:${port}: ${error.message}`, 'error'); this.log(`Error sending response to ${ip}:${port}: ${error.message}`, 'error')
} }
} }
} }
// Handle UDP server errors // Handle UDP server errors
private handleError(err: Error): void { private handleError(err: Error): void {
this.log(`UDP server error:\n${err.stack}`, 'error'); this.log(`UDP server error:\n${err.stack}`, 'error')
} }
// Handle when the UDP server starts listening // Handle when the UDP server starts listening
private handleListening(): void { private handleListening(): void {
const address = this.udpServer.address(); const address = this.udpServer.address()
this.log(`UDP server listening on ${address.address}:${address.port}`); this.log(`UDP server listening on ${address.address}:${address.port}`)
} }
} }
+22 -29
View File
@@ -1,50 +1,43 @@
import { BackupRetrievalWorker } from '../helpers/backup_retrieval'; import { BackupRetrievalWorker } from '../helpers/backup_retrieval'
import dotenv from 'dotenv'; import dotenv from 'dotenv'
// Load environment variables from .env file if it exists // Load environment variables from .env file if it exists
dotenv.config(); dotenv.config()
// Retrieve configuration from environment variables // Retrieve configuration from environment variables
const userConfigPath = process.env.USER_CONFIG_PATH as string; const clientPort = Number(process.env.CLIENT_PORT)
const applicationInfoPath = process.env.APPLICATION_INFO_PATH as string; const destinationPath = process.env.DESTINATION_PATH as string
const clientPort = Number(process.env.CLIENT_PORT); const pathToDatabaseFile = process.env.DATABASE_FILE_PATH as string
const destinationPath = process.env.DESTINATION_PATH as string;
// Validate that all required environment variables are present // Validate that all required environment variables are present
if (!userConfigPath || !applicationInfoPath || !clientPort || !destinationPath) { if (!pathToDatabaseFile || !clientPort || !destinationPath) {
console.error('Error: Missing required environment variables.'); console.error('Error: Missing required environment variables.')
process.exit(1); process.exit(1)
} }
// Initialize the BackupRetrievalWorker // Initialize the BackupRetrievalWorker
const backupRetrievalWorker = new BackupRetrievalWorker( const backupRetrievalWorker = new BackupRetrievalWorker(
userConfigPath, pathToDatabaseFile,
applicationInfoPath,
clientPort, clientPort,
destinationPath destinationPath,
); )
// Start the backup retrieval process // Start the backup retrieval process
backupRetrievalWorker.start().then(() => { backupRetrievalWorker.start().then(() => {
console.log('Backup retrieval process completed successfully.'); console.log('Backup retrieval process completed successfully.')
}); })
process.on('SIGTERM', async () => { process.on('SIGTERM', async () => {
console.log('Received SIGTERM. Cleaning up...'); console.log('Received SIGTERM. Cleaning up...')
await cleanupAndExit(); await cleanupAndExit()
}); })
process.on('SIGINT', async () => { process.on('SIGINT', async () => {
console.log('Received SIGINT. Cleaning up...'); console.log('Received SIGINT. Cleaning up...')
await cleanupAndExit(); await cleanupAndExit()
}); })
async function cleanupAndExit() { async function cleanupAndExit() {
// Perform any cleanup, such as closing connections, saving data, etc. console.log('Cleanup complete. Exiting.')
// Example: if you have a server instance running, you may want to close it: process.exit(0)
// await server.close();
console.log('Cleanup complete. Exiting.');
process.exit(0); // Exit with code 0 to indicate a clean exit
} }
+18 -28
View File
@@ -1,44 +1,34 @@
import { DirectoryWatcher } from "../helpers/directory_watcher"; import { DirectoryWatcher } from '../helpers/directory_watcher'
import dotenv from 'dotenv'; import dotenv from 'dotenv'
// Load environment variables from .env file if it exists // Load environment variables from .env file if it exists
dotenv.config(); dotenv.config()
// Retrieve configuration from environment variables const pathToDatabaseFile = process.env.DATABASE_FILE_PATH as string
const memoryManagerPath = process.env.MEMORY_MANAGER_PATH as string;
const applicationInfoPath = process.env.APPLICATION_INFO_PATH as string;
// Validate that all required environment variables are present // Validate that all required environment variables are present
if (!memoryManagerPath || !applicationInfoPath) { if (!pathToDatabaseFile) {
console.error('Error: Missing required environment variables.'); console.error('Error: Missing required environment variables.')
process.exit(1); process.exit(1)
} }
// Initialize and start DirectoryWatcher instances // Initialize and start DirectoryWatcher instances
const backupDirectoryManager = new DirectoryWatcher(memoryManagerPath, applicationInfoPath, 'backupDirectory'); const watcher = new DirectoryWatcher(pathToDatabaseFile)
backupDirectoryManager.start(); watcher.start()
const departmentShareManager = new DirectoryWatcher(memoryManagerPath, applicationInfoPath, 'departmentDirectory');
departmentShareManager.start();
const shareFileManager = new DirectoryWatcher(memoryManagerPath, applicationInfoPath, 'shareDirectory');
shareFileManager.start();
process.on('SIGTERM', async () => { process.on('SIGTERM', async () => {
console.log('Received SIGTERM. Cleaning up...'); console.log('Received SIGTERM. Cleaning up...')
await cleanupAndExit(); await cleanupAndExit()
}); })
process.on('SIGINT', async () => { process.on('SIGINT', async () => {
console.log('Received SIGINT. Cleaning up...'); console.log('Received SIGINT. Cleaning up...')
await cleanupAndExit(); await cleanupAndExit()
}); })
async function cleanupAndExit() { async function cleanupAndExit() {
backupDirectoryManager.closeWatcher(); watcher.stopAllWatchers()
departmentShareManager.closeWatcher();
shareFileManager.closeWatcher();
console.log('Cleanup complete. Exiting.'); console.log('Cleanup complete. Exiting.')
process.exit(0); // Exit with code 0 to indicate a clean exit process.exit(0) // Exit with code 0 to indicate a clean exit
} }
+20 -22
View File
@@ -1,39 +1,37 @@
import { parentPort } from 'worker_threads'; import { parentPort } from 'worker_threads'
import { NetworkScanner } from '../helpers/network_scanner'; import { NetworkScanner } from '../helpers/network_scanner'
// Extract data from environment variables // Extract data from environment variables
const udpPort = parseInt(process.env.UDP_PORT || '0', 10); const udpPort = parseInt(process.env.UDP_PORT || '0', 10)
const tcpPort = parseInt(process.env.TCP_PORT || '0', 10); const tcpPort = parseInt(process.env.TCP_PORT || '0', 10)
const okPage = process.env.OK_PAGE || ''; const okPage = process.env.OK_PAGE || ''
const errorPage = process.env.ERROR_PAGE || ''; const errorPage = process.env.ERROR_PAGE || ''
const databaseResetPage = process.env.DATABASE_RESET_PAGE || ''; const databaseResetPage = process.env.DATABASE_RESET_PAGE || ''
const userConfigPath = process.env.USER_CONFIG_PATH || ''; const pathToDatabaseFile = process.env.DATABASE_FILE_PATH || ''
const applicationInfoPath = process.env.APPLICATION_INFO_PATH || '';
// Start the NetworkScanner instance // Start the NetworkScanner instance
const networkScanner = new NetworkScanner( const networkScanner = new NetworkScanner(
applicationInfoPath, pathToDatabaseFile,
userConfigPath,
udpPort, udpPort,
tcpPort, tcpPort,
okPage, okPage,
errorPage, errorPage,
databaseResetPage databaseResetPage,
); )
process.on('SIGTERM', async () => { process.on('SIGTERM', async () => {
console.log('Received SIGTERM. Cleaning up...'); console.log('Received SIGTERM. Cleaning up...')
await cleanupAndExit(); await cleanupAndExit()
}); })
process.on('SIGINT', async () => { process.on('SIGINT', async () => {
console.log('Received SIGINT. Cleaning up...'); console.log('Received SIGINT. Cleaning up...')
await cleanupAndExit(); await cleanupAndExit()
}); })
async function cleanupAndExit() { async function cleanupAndExit() {
networkScanner.stopAllIntervals(); networkScanner.stopAllIntervals()
console.log('Cleanup complete. Exiting.'); console.log('Cleanup complete. Exiting.')
process.exit(0); // Exit with code 0 to indicate a clean exit process.exit(0) // Exit with code 0 to indicate a clean exit
} }
+26 -29
View File
@@ -1,43 +1,40 @@
import { UsersInfoFetcher } from '../helpers/users_info_fetcher'; import { UsersInfoFetcher } from '../helpers/users_info_fetcher'
import { BackupManager } from '../helpers/backup_manager'; import { BackupManager } from '../helpers/backup_manager'
import { FileSharer } from '../helpers/file_sharer'; import { FileSharer } from '../helpers/file_sharer'
import { DepartmentSharer } from '../helpers/department_sharer'; import { DepartmentSharer } from '../helpers/department_sharer'
// Retrieve data from environment variables // Retrieve data from environment variables
const usersConfigPath = process.env.USERS_CONFIG_PATH || ''; const pathToDatabaseFile = process.env.DATABASE_FILE_PATH || ''
const applicationInfoPath = process.env.APPLICATION_INFO_PATH || ''; const tcpPort = parseInt(process.env.TCP_PORT || '0', 10)
const memoryManagerPath = process.env.MEMORY_MANAGER_PATH || '';
const queueManagerPath = process.env.QUEUE_MANAGER_PATH || '';
const tcpPort = parseInt(process.env.TCP_PORT || '0', 10);
const usersInfoFetcher = new UsersInfoFetcher(applicationInfoPath, memoryManagerPath, tcpPort); const usersInfoFetcher = new UsersInfoFetcher(pathToDatabaseFile, tcpPort)
usersInfoFetcher.start(); usersInfoFetcher.start()
const backupManager = new BackupManager(usersConfigPath, applicationInfoPath, memoryManagerPath, tcpPort); const backupManager = new BackupManager(pathToDatabaseFile, tcpPort)
backupManager.start(); backupManager.start()
const fileSharer = new FileSharer(queueManagerPath, tcpPort); const departmentSharer = new DepartmentSharer(pathToDatabaseFile, tcpPort)
fileSharer.start(); departmentSharer.start()
const departmentSharer = new DepartmentSharer(usersConfigPath, applicationInfoPath, memoryManagerPath, tcpPort); const fileSharer = new FileSharer(pathToDatabaseFile, tcpPort)
departmentSharer.start(); fileSharer.start()
process.on('SIGTERM', async () => { process.on('SIGTERM', async () => {
console.log('Received SIGTERM. Cleaning up...'); console.log('Received SIGTERM. Cleaning up...')
await cleanupAndExit(); await cleanupAndExit()
}); })
process.on('SIGINT', async () => { process.on('SIGINT', async () => {
console.log('Received SIGINT. Cleaning up...'); console.log('Received SIGINT. Cleaning up...')
await cleanupAndExit(); await cleanupAndExit()
}); })
async function cleanupAndExit() { async function cleanupAndExit() {
usersInfoFetcher.stop(); usersInfoFetcher.stop()
await backupManager.stop(); await backupManager.stop()
await fileSharer.stop(); await fileSharer.stop()
await departmentSharer.stop(); await departmentSharer.stop()
console.log('Cleanup complete. Exiting.'); console.log('Cleanup complete. Exiting.')
process.exit(0); // Exit with code 0 to indicate a clean exit process.exit(0) // Exit with code 0 to indicate a clean exit
} }
+17 -17
View File
@@ -1,36 +1,36 @@
import { UdpServer } from "../network/udp/udp_server"; import { UdpServer } from '../network/udp/udp_server'
import { TcpServer } from "../network/tcp/tcp_server"; import { TcpServer } from '../network/tcp/tcp_server'
let udpServer: UdpServer | null let udpServer: UdpServer | null
let tcpServer: TcpServer | null let tcpServer: TcpServer | null
// Retrieve data from environment variables // Retrieve data from environment variables
const USER_UDP_PORT = parseInt(process.env.USER_UDP_PORT || '0', 10); const USER_UDP_PORT = parseInt(process.env.USER_UDP_PORT || '0', 10)
const USER_TCP_PORT = parseInt(process.env.USER_TCP_PORT || '0', 10); const USER_TCP_PORT = parseInt(process.env.USER_TCP_PORT || '0', 10)
const HOST = process.env.HOST || ''; const HOST = process.env.HOST || ''
// Initialize and start the servers // Initialize and start the servers
udpServer = new UdpServer(HOST, USER_UDP_PORT); udpServer = new UdpServer(HOST, USER_UDP_PORT)
udpServer.start(); udpServer.start()
tcpServer = new TcpServer(HOST, USER_TCP_PORT); tcpServer = new TcpServer(HOST, USER_TCP_PORT)
tcpServer.start(); tcpServer.start()
process.on('SIGTERM', async () => { process.on('SIGTERM', async () => {
console.log('Received SIGTERM. Cleaning up...'); console.log('Received SIGTERM. Cleaning up...')
await cleanupAndExit(); await cleanupAndExit()
}); })
process.on('SIGINT', async () => { process.on('SIGINT', async () => {
console.log('Received SIGINT. Cleaning up...'); console.log('Received SIGINT. Cleaning up...')
await cleanupAndExit(); await cleanupAndExit()
}); })
async function cleanupAndExit() { async function cleanupAndExit() {
// Perform any cleanup, such as closing connections, saving data, etc. // Perform any cleanup, such as closing connections, saving data, etc.
// Example: if you have a server instance running, you may want to close it: // Example: if you have a server instance running, you may want to close it:
// await server.close(); // await server.close();
console.log('Cleanup complete. Exiting.'); console.log('Cleanup complete. Exiting.')
process.exit(0); // Exit with code 0 to indicate a clean exit process.exit(0) // Exit with code 0 to indicate a clean exit
} }
+1 -1
View File
@@ -9,7 +9,7 @@
"moduleResolution": "node" "moduleResolution": "node"
}, },
"include": [ "include": [
"src/**/*" "src/**/*.ts"
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules"