async plugins fixed

This commit is contained in:
andrei-mihnea-cerbu
2024-11-12 18:05:50 +02:00
parent 4fb58b9727
commit 44df437425
22 changed files with 1690 additions and 2262 deletions
@@ -1,40 +0,0 @@
import { ParsedMessage } from '../message_handler';
import { OperationHandler } from './operation_handler';
import { OperationPlugin } from './operation_plugin';
export abstract class OperationBase implements OperationPlugin {
// Shared operation codes
public static readonly operationCodes = {
OK: 'OK',
ERR: 'ERR',
END: 'END',
};
// Default handler for OK operation
public static handleOk(parsedMessage: ParsedMessage): ParsedMessage {
return parsedMessage; // Typically, you would just acknowledge with OK, returning as-is
}
// Default handler for ERR operation
public static handleErr(parsedMessage: ParsedMessage): ParsedMessage {
return parsedMessage; // Typically, you would log the error and return
}
// Default handler for END operation
public static handleEnd(parsedMessage: ParsedMessage): ParsedMessage {
return {
operationCode: OperationBase.operationCodes.END,
metaInfo: { message: 'Connection ended.' },
};
}
// Register the common OK, ERR, and END handlers
public static registerCommonOperations(operationHandler: OperationHandler): void {
operationHandler.registerHandler(OperationBase.operationCodes.OK, OperationBase.handleOk);
operationHandler.registerHandler(OperationBase.operationCodes.ERR, OperationBase.handleErr);
operationHandler.registerHandler(OperationBase.operationCodes.END, OperationBase.handleEnd);
}
// Abstract register method that will be implemented by subclasses
public abstract register(operationHandler: OperationHandler): void;
}
@@ -2,14 +2,14 @@
import { ParsedMessage, MessageHandler } from '../message_handler'; import { ParsedMessage, MessageHandler } from '../message_handler';
import { OperationPlugin } from './operation_plugin'; import { OperationPlugin } from './operation_plugin';
type OperationHandlerFunction = (parsedMessage: ParsedMessage) => ParsedMessage; // Define handler function type to return 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() {
// Register only the unknown command handler on initialization
this.registerHandler('UNKNOWN_COMMAND', this.handleUnknownCommand); this.registerHandler('UNKNOWN_COMMAND', this.handleUnknownCommand);
} }
@@ -26,25 +26,24 @@ export class OperationHandler {
this.handlers[operationCode] = handler; this.handlers[operationCode] = handler;
} }
// Handle operation request // Handle operation request asynchronously
public handleOperation(rawMessage: string): ParsedMessage { public async handleOperation(rawMessage: string): Promise<ParsedMessage> {
// Parse and validate the message
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);
} }
// Dispatch the handler for the given operation code // 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 handler(parsedMessage); return await handler(parsedMessage);
} else { } else {
return this.handleUnknownCommand(parsedMessage); return this.handleUnknownCommand(parsedMessage);
} }
} }
// Default handler for unknown commands // Default handler for unknown commands
private handleUnknownCommand(parsedMessage: ParsedMessage): ParsedMessage { private async handleUnknownCommand(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
return { return {
operationCode: 'UNKNOWN_COMMAND', operationCode: 'UNKNOWN_COMMAND',
metaInfo: { message: 'Unknown command received.' }, metaInfo: { message: 'Unknown command received.' },
@@ -1,25 +1,26 @@
import { ParsedMessage } from '../message_handler'; import { ParsedMessage } from '../message_handler';
import { OperationBase } from '../operations_base/operation_base';
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";
export class GeneralOperations extends OperationBase { export class GeneralOperations implements OperationPlugin {
public static readonly operationCodes = { public static readonly operationCodes = {
...OperationBase.operationCodes, // Inherit common operation codes (OK, ERR, END) OK: 'OK',
ERR: 'ERR',
END: 'END',
HEARTBEAT: 'HEARTBEAT', HEARTBEAT: 'HEARTBEAT',
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 // Handle heartbeat operation asynchronously
public static handleHeartbeat(): ParsedMessage { public static async handleHeartbeat(): 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)) {
// @ts-ignore 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;
@@ -34,8 +35,8 @@ export class GeneralOperations extends OperationBase {
}; };
} }
// Handle public key exchange // Handle public key exchange asynchronously
public static handlePublicKey(parsedMessage: ParsedMessage): 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 {
@@ -50,14 +51,14 @@ export class GeneralOperations extends OperationBase {
} }
} }
// Handle AES key exchange // Handle AES key exchange asynchronously
public static handleAESKey(parsedMessage: ParsedMessage): 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: aesKey, aesIv: aesIv }, metaInfo: { aesKey, aesIv },
}; };
} else { } else {
return { return {
@@ -67,14 +68,22 @@ export class GeneralOperations extends OperationBase {
} }
} }
// Default async handler for OK operation
public static async handleOk(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
return parsedMessage; // Acknowledge with OK, returning as-is
}
// Default async handler for ERR operation
public static async handleErr(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
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 {
// Register specific handlers for the general operations
operationHandler.registerHandler(GeneralOperations.operationCodes.HEARTBEAT, GeneralOperations.handleHeartbeat); operationHandler.registerHandler(GeneralOperations.operationCodes.HEARTBEAT, GeneralOperations.handleHeartbeat);
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_PUBLIC_KEY, GeneralOperations.handlePublicKey); operationHandler.registerHandler(GeneralOperations.operationCodes.SET_PUBLIC_KEY, GeneralOperations.handlePublicKey);
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_AES_KEY, GeneralOperations.handleAESKey); // Register AES key handler operationHandler.registerHandler(GeneralOperations.operationCodes.SET_AES_KEY, GeneralOperations.handleAESKey);
operationHandler.registerHandler(GeneralOperations.operationCodes.OK, GeneralOperations.handleOk);
// Register common operations inherited from the base class operationHandler.registerHandler(GeneralOperations.operationCodes.ERR, GeneralOperations.handleErr);
OperationBase.registerCommonOperations(operationHandler);
} }
} }
@@ -1,18 +1,16 @@
import { ParsedMessage } from '../message_handler'; import { ParsedMessage } from '../message_handler';
import { OperationBase } from '../operations_base/operation_base';
import { OperationHandler } from '../operations_base/operation_handler'; import { OperationHandler } from '../operations_base/operation_handler';
import {operationCodes} from "../operation_codes";
import path from 'path'; import path from 'path';
import { execSync } from 'child_process'; import fs from 'fs/promises';
import fs from 'fs'; import checkDiskSpace from "check-disk-space";
import { JsonManager } from '../../helpers/json_manager';
import {OperationPlugin} from "../operations_base/operation_plugin";
const LOCK_FILE_EXTENSION = '.lock'; export class UserToUserOperations implements OperationPlugin {
export class UserToUserOperations extends OperationBase {
public static readonly operationCodes = { public static readonly operationCodes = {
...OperationBase.operationCodes, // Inherit common operation codes (OK, ERR, END)
SEND_ANNOUNCEMENT: 'SEND_ANNOUNCEMENT', SEND_ANNOUNCEMENT: 'SEND_ANNOUNCEMENT',
GET_USER_INFORMATION: 'GET_USER_INFORMATION', GET_USER_INFORMATION: 'GET_USER_INFORMATION',
RESET_DATABASE: 'RESET_DATABASE',
BACKUP_FILE: 'BACKUP_FILE', BACKUP_FILE: 'BACKUP_FILE',
CLEAR_BACKUP: 'CLEAR_BACKUP', CLEAR_BACKUP: 'CLEAR_BACKUP',
SHARE_FILE: 'SHARE_FILE', SHARE_FILE: 'SHARE_FILE',
@@ -24,429 +22,149 @@ export class UserToUserOperations extends OperationBase {
DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE', DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE',
}; };
// Utility function to pause execution (sleep) private static async hasEnoughDiskSpace(directory: string, requiredPercentage: number = 25): Promise<boolean> {
static sleep(ms: number): void {
const start = Date.now();
while (Date.now() - start < ms) {
// Busy wait loop (not optimal but fine for this short duration)
}
}
// Read JSON file with a lock mechanism
static readJsonSync(filePath: string): any {
const lockFilePath = `${filePath}.${LOCK_FILE_EXTENSION}`;
const absolutePath = path.resolve(filePath);
try { try {
// Loop until the lock file is removed by another process const diskInfo = await checkDiskSpace(directory);
while (fs.existsSync(lockFilePath)) { const availableSpace = diskInfo.free;
console.log(`Waiting for lock file to be released: ${lockFilePath}`); const totalSpace = diskInfo.size;
UserToUserOperations.sleep(100);
}
// Create lock file to signal this process is working on the file
fs.writeFileSync(lockFilePath, ''); // Create the lock file
// Read and parse the JSON file
const fileContents = fs.readFileSync(absolutePath, 'utf-8');
const parsedJson = JSON.parse(fileContents);
// Once processing is done, delete the lock file
fs.unlinkSync(lockFilePath); // Remove the lock file
return parsedJson; // Return the parsed JSON data
} catch (error) {
console.error(`Error reading or parsing JSON from ${filePath}:`, error);
// Ensure the lock file is removed even in case of an error
if (fs.existsSync(lockFilePath)) {
fs.unlinkSync(lockFilePath);
}
return null; // Return null or throw error based on preference
}
}
static writeJsonSync(filePath: string, data: any): boolean {
const lockFilePath = `${filePath}.${LOCK_FILE_EXTENSION}`;
const absolutePath = path.resolve(filePath);
try {
// Loop until the lock file is removed by another process
while (fs.existsSync(lockFilePath)) {
console.log(`Waiting for lock file to be released: ${lockFilePath}`);
UserToUserOperations.sleep(100); // Use the sleep utility to pause
}
// Create lock file to signal this process is working on the file
fs.writeFileSync(lockFilePath, ''); // Create the lock file
// Write the data to the JSON file
fs.writeFileSync(absolutePath, JSON.stringify(data, null, 2), 'utf-8');
console.log(`Data written successfully to ${absolutePath}`);
// Once processing is done, delete the lock file
fs.unlinkSync(lockFilePath); // Remove the lock file
return true; // Indicate successful write
} catch (error) {
console.error(`Error writing JSON to ${filePath}:`, error);
// Ensure the lock file is removed even in case of an error
if (fs.existsSync(lockFilePath)) {
fs.unlinkSync(lockFilePath);
}
return false; // Indicate failure
}
}
private static hasEnoughDiskSpace(directory: string, requiredPercentage: number): boolean {
try {
let availableSpace = 0;
let totalSpace = 0;
if (process.platform === 'win32') {
// Windows
const output = execSync(`wmic logicaldisk where "DeviceID='${directory[0]}:'" get FreeSpace,Size`).toString();
const lines = output.trim().split('\n');
const [freeSpaceStr, totalSpaceStr] = lines[1].trim().split(/\s+/);
availableSpace = parseInt(freeSpaceStr, 10); // Available space in bytes
totalSpace = parseInt(totalSpaceStr, 10); // Total space in bytes
} else {
// Unix-based (Linux/macOS)
const output = execSync(`df -k "${directory}"`).toString();
const lines = output.trim().split('\n');
const parts = lines[lines.length - 1].split(/\s+/);
const availableSpaceInKb = parseInt(parts[3], 10); // Available space in KB
const totalSpaceInKb = parseInt(parts[1], 10); // Total space in KB
availableSpace = availableSpaceInKb * 1024;
totalSpace = totalSpaceInKb * 1024;
}
// Calculate available space as a percentage of the total space
const availablePercentage = (availableSpace / totalSpace) * 100; const availablePercentage = (availableSpace / totalSpace) * 100;
// Return true if the available percentage is greater than or equal to the required percentage
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 if there's an error return false;
} }
} }
public static handleResetDatabase(parsedMessage: ParsedMessage): ParsedMessage {
// Path to the application.json file
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
public static async handleSendAnnouncement(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
if (!parsedMessage.metaInfo?.message) {
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing announcement message.' }};
}
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json'));
try { try {
// Read the existing data from application.json await jsonManager.writeValue('announcement', parsedMessage.metaInfo.message);
const appData = UserToUserOperations.readJsonSync(pathToApplicationJson) || {}; console.log(`Announcement saved: ${parsedMessage.metaInfo.message}`);
return { operationCode: operationCodes.OK, metaInfo: { message: 'Announcement saved.' }};
// Update the reset_application_preferences field to true
appData.reset_application_preferences = true;
// Write the updated data back to application.json
const success = UserToUserOperations.writeJsonSync(pathToApplicationJson, appData);
if (success) {
console.log(`Application preferences reset successfully.`);
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { message: 'Application preferences reset successfully.' },
};
} else {
throw new Error("Failed to write to application.json");
}
} catch (error: any) { } catch (error: any) {
console.error(`Error resetting application preferences: ${error.message}`); console.error(`Error saving announcement: ${error.message}`);
return { return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` }};
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error resetting application preferences: ${error.message}` },
};
} }
} }
public static async handleGetUserInformation(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
public static handleSendAnnouncement(parsedMessage: ParsedMessage): ParsedMessage { const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'userConfig.json'));
// Ensure the message is available in metaInfo
if (!parsedMessage.metaInfo || !parsedMessage.metaInfo.message) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing announcement message in meta information.' },
};
}
const announcementMessage = parsedMessage.metaInfo.message;
// Path to the application.json file
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
try { try {
// Read the existing data from application.json const userInfo = await jsonManager.readValue('user_info');
const appData = UserToUserOperations.readJsonSync(pathToApplicationJson) || {}; return { operationCode: operationCodes.OK, metaInfo: userInfo };
// Update the announcement field with the new message
appData.announcement = announcementMessage;
// Write the updated data back to application.json
fs.writeFileSync(pathToApplicationJson, JSON.stringify(appData, null, 2), 'utf-8');
console.log(`Announcement message saved successfully: ${announcementMessage}`);
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { message: 'Announcement message saved successfully.' },
};
} catch (error: any) { } catch (error: any) {
console.error(`Error saving announcement message: ${error.message}`); console.error(`Error fetching user info: ${error.message}`);
return { return { operationCode: operationCodes.ERR, metaInfo: { message: 'Error fetching user info' }};
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error saving announcement message: ${error.message}` },
};
} }
} }
// Handle user information retrieval public static async handleBackupFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
public static handleGetUserInformation(parsedMessage: ParsedMessage): ParsedMessage { if (!parsedMessage.metaInfo?.userName || !parsedMessage.metaInfo?.relativeFilePath || !parsedMessage.fileContent) {
const pathToUserJson = path.join(__dirname, '..', '..', 'json_files', 'userConfig.json'); return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing file or user information.' }};
const userInfo = UserToUserOperations.readJsonSync(pathToUserJson);
if (userInfo) {
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: userInfo.user_info,
};
} else {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Error fetching user information.' },
};
}
}
// Handle file reception and saving
public static handleBackupFile(parsedMessage: ParsedMessage): ParsedMessage {
// Ensure metaInfo and fileContent are available
if (!parsedMessage.metaInfo || !parsedMessage.fileContent) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing file content or meta information.' },
};
} }
const { userName, relativeFilePath } = parsedMessage.metaInfo; const { userName, relativeFilePath } = parsedMessage.metaInfo;
const fullFilePath = path.join(__dirname, '..', '..', 'backups', userName, relativeFilePath);
if (!userName || !relativeFilePath) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing user name or file path information.' },
};
}
// Base directory where backups will be stored
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
// Full path where the file will be stored (under the user's directory)
const fullFilePath = path.join(baseBackupDir, userName, relativeFilePath);
try { try {
// Check if there is enough disk space if (!await UserToUserOperations.hasEnoughDiskSpace(path.dirname(fullFilePath), 25)) {
const requiredPercentage = 25; return { operationCode: operationCodes.ERR, metaInfo:
if (!UserToUserOperations.hasEnoughDiskSpace(baseBackupDir, requiredPercentage)) { { message: 'Insufficient disk space.' }};
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Insufficient disk space for backup.' },
};
} }
// Ensure the directory structure exists (create directories if they don't exist) await fs.mkdir(path.dirname(fullFilePath), { recursive: true });
const dirPath = path.dirname(fullFilePath); await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer);
if (!fs.existsSync(dirPath)) { console.log(`File saved: ${fullFilePath}`);
fs.mkdirSync(dirPath, { recursive: true });
}
// Write the file content to the correct path return { operationCode: operationCodes.OK, metaInfo: { message: `File saved: ${relativeFilePath}` }};
fs.writeFileSync(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64');
console.log(`File saved successfully: ${fullFilePath}`);
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { message: `File saved successfully: ${relativeFilePath}` },
};
} catch (error: any) { } catch (error: any) {
console.error(`Error saving file: ${error.message}`); console.error(`Error saving file: ${error.message}`);
return { return { operationCode: operationCodes.ERR, metaInfo: { message: `Error saving file: ${error.message}` }};
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error saving file: ${error.message}` },
};
} }
} }
// Handle clearing all backups for a user public static async handleClearBackup(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
public static handleClearBackup(parsedMessage: ParsedMessage): ParsedMessage { if (!parsedMessage.metaInfo?.userName) {
// Ensure the userName is available in metaInfo return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' }};
if (!parsedMessage.metaInfo || !parsedMessage.metaInfo.userName) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing user name in meta information.' },
};
} }
const { userName } = parsedMessage.metaInfo; const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.userName);
// Base directory where backups are stored
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
const userBackupDir = path.join(baseBackupDir, userName);
try { try {
// Check if the user's backup directory exists await fs.rm(userBackupDir, { recursive: true, force: true });
if (fs.existsSync(userBackupDir)) { console.log(`Backup cleared: ${userBackupDir}`);
// Recursively delete the user's backup directory return { operationCode: operationCodes.OK, metaInfo: { message: `Backup cleared for ${parsedMessage.metaInfo.userName}` }};
fs.rmSync(userBackupDir, { recursive: true, force: true });
console.log(`Backup cleared successfully for user: ${userName}`);
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { message: `Backup cleared successfully for user: ${userName}` },
};
} else {
console.warn(`Backup directory not found for user: ${userName}`);
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Backup directory not found for user: ${userName}` },
};
}
} catch (error: any) { } catch (error: any) {
console.error(`Error clearing backup for user: ${userName} - ${error.message}`); console.error(`Error clearing backup: ${error.message}`);
return { return { operationCode: operationCodes.ERR, metaInfo: { message: `Error clearing backup: ${error.message}` }};
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error clearing backup for user: ${userName}` },
};
} }
} }
// Handle sharing file operation public static async handleShareFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
public static handleShareFile(parsedMessage: ParsedMessage): ParsedMessage { if (!parsedMessage.metaInfo?.userName || !parsedMessage.metaInfo?.relativeFilePath || !parsedMessage.fileContent) {
// Ensure metaInfo and fileContent are available return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing share info.' }};
if (!parsedMessage.metaInfo || !parsedMessage.fileContent) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing file content or meta information.' },
};
} }
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json'));
const { userName, relativeFilePath } = parsedMessage.metaInfo; const { userName, relativeFilePath } = parsedMessage.metaInfo;
if (!userName || !relativeFilePath) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing user name or file path information.' },
};
}
// Path to the application.json to read the shareDirectory field
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
const appInfo = UserToUserOperations.readJsonSync(pathToApplicationJson);
// Corrected the condition
if (!appInfo || !appInfo.shareDirectory) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Error retrieving share directory from application.json.' },
};
}
// Get the share directory path
const shareDirectory = appInfo.shareDirectory.path;
// Full path where the file will be stored (under the user's directory in the shared folder)
const fullFilePath = path.join(shareDirectory, userName, relativeFilePath);
try { try {
// Ensure the directory structure exists (create directories if they don't exist) const appInfo = await jsonManager.readValue('shareDirectory');
const dirPath = path.dirname(fullFilePath); const shareDirectory = appInfo?.path || '';
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true }); if (!shareDirectory) {
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Share directory missing.' }};
} }
// Write the file content to the correct path const fullFilePath = path.join(shareDirectory, userName, relativeFilePath);
fs.writeFileSync(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64'); await fs.mkdir(path.dirname(fullFilePath), { recursive: true });
await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer);
console.log(`File shared: ${fullFilePath}`);
console.log(`File shared successfully: ${fullFilePath}`); return { operationCode: operationCodes.OK, metaInfo: { message: `File shared: ${relativeFilePath}` }};
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { message: `File shared successfully: ${relativeFilePath}` },
};
} catch (error: any) { } catch (error: any) {
console.error(`Error sharing file: ${error.message}`); console.error(`Error sharing file: ${error.message}`);
return { return { operationCode: operationCodes.ERR, metaInfo: { message: `Error sharing file: ${error.message}` }};
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error sharing file: ${error.message}` },
};
} }
} }
public static handleClearDepartment(parsedMessage: ParsedMessage): ParsedMessage { public static async handleClearDepartment(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
// Ensure the userName is available in metaInfo if (!parsedMessage.metaInfo?.userName) {
if (!parsedMessage.metaInfo || !parsedMessage.metaInfo.userName) { return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' }};
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing user name in meta information.' },
};
} }
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json'));
const { userName } = parsedMessage.metaInfo; const { userName } = parsedMessage.metaInfo;
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
const appInfo = UserToUserOperations.readJsonSync(pathToApplicationJson);
// Corrected the condition
if (!appInfo || !appInfo.departmentDirectory) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Error retrieving share directory from application.json.' },
};
}
// Get the share directory path
const baseDepartmentDir = appInfo.departmentDirectory.path;
const userDepartmentDir = path.join(baseDepartmentDir, '..', 'DEPARTMENT_FILES', userName);
try { try {
// Check if the user's backup directory exists const appInfo = await jsonManager.readValue('departmentDirectory');
if (fs.existsSync(userDepartmentDir)) { const departmentDir = appInfo?.path || '';
// Recursively delete the user's backup directory const userDepartmentDir = path.join(departmentDir, '..', 'DEPARTMENT_SHARE', userName);
fs.rmSync(userDepartmentDir, { recursive: true, force: true });
console.log(`Backup cleared successfully for user: ${userName}`);
return { try {
operationCode: UserToUserOperations.operationCodes.OK, await fs.access(userDepartmentDir)
metaInfo: { message: `Backup cleared successfully for user: ${userName}`}, }catch(ex: any){
}; return { operationCode: operationCodes.OK};
} else {
console.warn(`Backup directory not found for user: ${userName}`);
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Backup directory not found for user: ${userName}`},
};
} }
await fs.rm(userDepartmentDir, { recursive: true, force: true });
console.log(`Department backup cleared: ${userDepartmentDir}`);
return { operationCode: operationCodes.OK, metaInfo: { message: `Department backup cleared for ${userName}` }};
} catch (error: any) { } catch (error: any) {
console.error(`Error clearing backup for user: ${userName} - ${error.message}`); console.error(`Error clearing department: ${error.message}`);
return { return { operationCode: operationCodes.ERR, metaInfo: { message: `Error clearing department: ${error.message}` }};
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error clearing backup for user: ${userName}`},
};
} }
} }
public static handleDepartmentFile(parsedMessage: ParsedMessage): ParsedMessage { public static async handleDepartmentFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
// Ensure metaInfo and fileContent are available // Ensure metaInfo and fileContent are available
if (!parsedMessage.metaInfo || !parsedMessage.fileContent) { if (!parsedMessage.metaInfo || !parsedMessage.fileContent) {
return { return {
operationCode: UserToUserOperations.operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: 'Missing file content or meta information.' }, metaInfo: { message: 'Missing file content or meta information.' },
}; };
} }
@@ -455,130 +173,99 @@ export class UserToUserOperations extends OperationBase {
if (!userName || !relativeFilePath) { if (!userName || !relativeFilePath) {
return { return {
operationCode: UserToUserOperations.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 appInfo = UserToUserOperations.readJsonSync(pathToApplicationJson); const jsonManager = new JsonManager(pathToApplicationJson);
// Corrected the condition // Read application configuration asynchronously
if (!appInfo || !appInfo.departmentDirectory) { let appInfo;
try {
appInfo = await jsonManager.readValue('departmentDirectory');
} catch (error: any) {
return { return {
operationCode: UserToUserOperations.operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: 'Error retrieving share directory from application.json.' }, metaInfo: { message: `Error reading application config: ${error.message}` },
};
}
if (!appInfo || !appInfo.path) {
return {
operationCode: operationCodes.ERR,
metaInfo: { message: 'Error retrieving department directory from application.json.' },
}; };
} }
// Get the share directory path // Get the share directory path
const departmentDirectory = appInfo.departmentDirectory.path; const departmentDirectory = appInfo.path;
// Full path where the file will be stored (under the user's directory in the shared folder)
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);
if (!fs.existsSync(dirPath)) { await fs.mkdir(dirPath, { recursive: true });
fs.mkdirSync(dirPath, { recursive: true });
}
// Write the file content to the correct path // Write the file content to the specified path
fs.writeFileSync(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64'); await fs.writeFile(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64');
console.log(`File shared successfully: ${fullFilePath}`);
return { return {
operationCode: UserToUserOperations.operationCodes.OK, operationCode: operationCodes.OK,
metaInfo: { message: `File shared successfully: ${relativeFilePath}`}, metaInfo: { message: `File shared successfully: ${relativeFilePath}` },
}; };
} catch (error: any) { } catch (error: any) {
console.error(`Error sharing file: ${error.message}`);
return { return {
operationCode: UserToUserOperations.operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: {message: `Error sharing file: ${error.message}`}, metaInfo: { message: `Error sharing file: ${error.message}` },
}; };
} }
} }
// Check if a backup has been created for a user public static async handleIsBackupCreated(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
public static handleIsBackupCreated(parsedMessage: ParsedMessage): ParsedMessage { if (!parsedMessage.metaInfo?.name) {
if(!parsedMessage.metaInfo) { return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing name in meta information.' }};
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing metaInfo.' },
};
} }
const {name} = parsedMessage.metaInfo; const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name);
if (!name) {
return { try {
operationCode: UserToUserOperations.operationCodes.ERR, const exists = await fs.access(userBackupDir).then(() => true).catch(() => false);
metaInfo: { message: 'Missing name in meta information.' }, return { operationCode: operationCodes.OK, metaInfo: { backupExists: exists }};
}; } catch (error: any) {
console.error(`Error checking backup: ${error.message}`);
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error checking backup: ${error.message}` }};
} }
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
const userBackupDir = path.join(baseBackupDir, name);
console.log(userBackupDir);
const exists = fs.existsSync(userBackupDir);
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { backupExists: exists },
};
} }
// Get the structure of the backup directory for a user public static async handleGetBackupStructure(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
public static handleGetBackupStructure(parsedMessage: ParsedMessage): ParsedMessage { if (!parsedMessage.metaInfo?.name) {
if(!parsedMessage.metaInfo) { return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing name in meta information.' }};
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing metaInfo.' },
};
} }
const {name} = parsedMessage.metaInfo; const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name);
if (!name) {
return { try {
operationCode: UserToUserOperations.operationCodes.ERR, const structure = await UserToUserOperations.buildDirectoryStructure(userBackupDir);
metaInfo: { message: 'Missing name in meta information.' }, return { operationCode: operationCodes.OK, metaInfo: { structure }};
}; } catch (error: any) {
console.error(`Error building backup structure: ${error.message}`);
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` }};
} }
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
const userBackupDir = path.join(baseBackupDir, name);
if (!fs.existsSync(userBackupDir)) {
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { structure: {} }, // Return empty structure if directory doesn't exist
};
}
const fileStructure = UserToUserOperations.buildDirectoryStructure(userBackupDir);
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { structure: fileStructure },
};
} }
// Build the directory structure recursively private static async buildDirectoryStructure(directoryPath: string): Promise<any> {
private static buildDirectoryStructure(directoryPath: string): any {
const structure: any = {}; const structure: any = {};
const files = fs.readdirSync(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 = fs.statSync(filePath); const stats = await fs.stat(filePath);
if (stats.isDirectory()) { if (stats.isDirectory()) {
structure[file] = UserToUserOperations.buildDirectoryStructure(filePath); // Recursive for subdirectories structure[file] = await UserToUserOperations.buildDirectoryStructure(filePath);
} else { } else {
structure[file] = path.relative(directoryPath, filePath); structure[file] = path.relative(directoryPath, filePath);
} }
@@ -587,67 +274,43 @@ export class UserToUserOperations extends OperationBase {
return structure; return structure;
} }
// Handle file request from backup directory public static async handleReqFileFromBackup(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
public static handleReqFileFromBackup(parsedMessage: ParsedMessage): ParsedMessage { if (!parsedMessage.metaInfo?.name || !parsedMessage.metaInfo?.relativeFilePath) {
if(!parsedMessage.metaInfo) {
return { return {
operationCode: UserToUserOperations.operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: 'Missing metaInfo.' }, 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);
if (!name || !relativeFilePath) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing name or file path in meta information.' },
};
}
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
const userBackupDir = path.join(baseBackupDir, name);
const fullFilePath = path.join(userBackupDir, relativeFilePath);
if (!fs.existsSync(fullFilePath)) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'File not found in backup.' },
};
}
try { try {
const fileContent = fs.readFileSync(fullFilePath, 'base64'); const fileContent = await fs.readFile(fullFilePath);
return { return {
operationCode: UserToUserOperations.operationCodes.OK, operationCode: operationCodes.OK,
metaInfo: { relativeFilePath }, metaInfo: { relativeFilePath },
fileContent: Buffer.from(fileContent, 'base64') fileContent,
}; };
} catch (error: any) { } catch (error: any) {
console.error(`Error reading file: ${error.message}`); console.error(`Error reading file from backup: ${error.message}`);
return { return {
operationCode: UserToUserOperations.operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: `Error reading file: ${error.message}`}, metaInfo: { message: `Error reading file: ${error.message}` },
}; };
} }
} }
// Register user-to-user operations with the OperationHandler
public register(operationHandler: OperationHandler): void { public register(operationHandler: OperationHandler): void {
// Register specific handlers for user-to-user operations
operationHandler.registerHandler(UserToUserOperations.operationCodes.RESET_DATABASE, UserToUserOperations.handleResetDatabase);
operationHandler.registerHandler(UserToUserOperations.operationCodes.SEND_ANNOUNCEMENT, UserToUserOperations.handleSendAnnouncement); operationHandler.registerHandler(UserToUserOperations.operationCodes.SEND_ANNOUNCEMENT, UserToUserOperations.handleSendAnnouncement);
operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_USER_INFORMATION, UserToUserOperations.handleGetUserInformation); operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_USER_INFORMATION, UserToUserOperations.handleGetUserInformation);
operationHandler.registerHandler(UserToUserOperations.operationCodes.BACKUP_FILE, UserToUserOperations.handleBackupFile); operationHandler.registerHandler(UserToUserOperations.operationCodes.BACKUP_FILE, UserToUserOperations.handleBackupFile);
operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_BACKUP, UserToUserOperations.handleClearBackup); operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_BACKUP, UserToUserOperations.handleClearBackup);
operationHandler.registerHandler(UserToUserOperations.operationCodes.SHARE_FILE, UserToUserOperations.handleShareFile); operationHandler.registerHandler(UserToUserOperations.operationCodes.SHARE_FILE, UserToUserOperations.handleShareFile);
operationHandler.registerHandler(UserToUserOperations.operationCodes.DEPARTMENT_FILE, UserToUserOperations.handleDepartmentFile);
operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_DEPARTMENT, UserToUserOperations.handleClearDepartment); 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.IS_BACKUP_CREATED, UserToUserOperations.handleIsBackupCreated);
operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_BACKUP_STRUCTURE, UserToUserOperations.handleGetBackupStructure); operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_BACKUP_STRUCTURE, UserToUserOperations.handleGetBackupStructure);
operationHandler.registerHandler(UserToUserOperations.operationCodes.REQ_FILE_FROM_BACKUP, UserToUserOperations.handleReqFileFromBackup); operationHandler.registerHandler(UserToUserOperations.operationCodes.REQ_FILE_FROM_BACKUP, UserToUserOperations.handleReqFileFromBackup);
// Register common operations inherited from the base class
OperationBase.registerCommonOperations(operationHandler);
} }
} }
@@ -122,7 +122,7 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
for (let i = 0; i < messages.length - 1; i++) { for (let i = 0; i < messages.length - 1; i++) {
const completeMessage = messages[i]; const completeMessage = messages[i];
if (completeMessage) { if (completeMessage) {
this.processCompleteMessage(completeMessage); await this.processCompleteMessage(completeMessage);
} }
} }
@@ -130,7 +130,7 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
} }
} }
private processCompleteMessage(completeMessage: string): void { private async processCompleteMessage(completeMessage: string): Promise<void> {
const [headerJson, chunkContent] = completeMessage.split('|'); const [headerJson, chunkContent] = completeMessage.split('|');
const header = JSON.parse(headerJson); const header = JSON.parse(headerJson);
@@ -142,12 +142,12 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) { if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) {
const fullMessage = this.chunkBuffers[header.messageId].join(''); const fullMessage = this.chunkBuffers[header.messageId].join('');
this.handleIncomingMessage(fullMessage); await this.handleIncomingMessage(fullMessage);
delete this.chunkBuffers[header.messageId]; delete this.chunkBuffers[header.messageId];
} }
} }
handleIncomingMessage(incomingMessage: string): void { async handleIncomingMessage(incomingMessage: string): Promise<void> {
let messageToProcess = incomingMessage; let messageToProcess = incomingMessage;
if (this.aesKey && this.aesIv) { if (this.aesKey && this.aesIv) {
messageToProcess = this.decryptWithAes(incomingMessage); messageToProcess = this.decryptWithAes(incomingMessage);
@@ -155,7 +155,7 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
messageToProcess = this.decryptWithRsa(incomingMessage); messageToProcess = this.decryptWithRsa(incomingMessage);
} }
const result = 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;
@@ -176,6 +176,8 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
} }
getHandlerResult(): ParsedMessage | null { getHandlerResult(): ParsedMessage | null {
return this.handlerResult; const message = this.handlerResult;
this.handlerResult = null;
return message;
} }
} }
@@ -106,7 +106,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
for (let i = 0; i < messages.length - 1; i++) { for (let i = 0; i < messages.length - 1; i++) {
const completeMessage = messages[i]; const completeMessage = messages[i];
if (completeMessage) { if (completeMessage) {
this.processCompleteMessage(completeMessage); await this.processCompleteMessage(completeMessage);
} }
} }
@@ -114,7 +114,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
} }
} }
private processCompleteMessage(completeMessage: string): void { private async processCompleteMessage(completeMessage: string): Promise<void> {
const [headerJson, chunkContent] = completeMessage.split('|'); const [headerJson, chunkContent] = completeMessage.split('|');
const header = JSON.parse(headerJson); const header = JSON.parse(headerJson);
@@ -126,7 +126,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) { if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) {
const fullMessage = this.chunkBuffers[header.messageId].join(''); const fullMessage = this.chunkBuffers[header.messageId].join('');
this.handleIncomingMessage(fullMessage); await this.handleIncomingMessage(fullMessage);
delete this.chunkBuffers[header.messageId]; delete this.chunkBuffers[header.messageId];
} }
} }
@@ -169,14 +169,14 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
} }
// Handle incoming message (decrypt with AES if available) // Handle incoming message (decrypt with AES if available)
handleIncomingMessage(incomingMessage: string): void { async handleIncomingMessage(incomingMessage: string): Promise<void> {
let messageToProcess = incomingMessage; let messageToProcess = incomingMessage;
if (this.aesKey && this.aesIv) { if (this.aesKey && this.aesIv) {
messageToProcess = this.decryptWithAes(incomingMessage); messageToProcess = this.decryptWithAes(incomingMessage);
} }
this.handlerResult = this.operationHandler.handleOperation(messageToProcess); this.handlerResult = await this.operationHandler.handleOperation(messageToProcess);
} }
// Write message to socket // Write message to socket
@@ -12,8 +12,8 @@ export class UdpSocketCommunicator extends SocketCommunicatorBase {
} }
// Handle incoming message (no decryption needed for UDP) // Handle incoming message (no decryption needed for UDP)
handleIncomingMessage(incomingMessage: string): void { async handleIncomingMessage(incomingMessage: string): Promise<void> {
this.handlerResult = this.operationHandler.handleOperation(incomingMessage); this.handlerResult = await this.operationHandler.handleOperation(incomingMessage);
} }
// Send plain message over UDP (metaInfo as JSON and fileContent as Buffer) // Send plain message over UDP (metaInfo as JSON and fileContent as Buffer)
+23 -24
View File
@@ -14,6 +14,7 @@ export class TcpServer {
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();
constructor(host: string, port: number) { constructor(host: string, port: number) {
this.connectionManager = new ConnectionManager(); this.connectionManager = new ConnectionManager();
@@ -25,21 +26,14 @@ export class TcpServer {
this.operationHandler.loadPlugin(new UserToUserOperations()); this.operationHandler.loadPlugin(new UserToUserOperations());
} }
// Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void { private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[TcpServer]'; const prefix = '[TcpServer]';
if (level === 'error') { console[level === 'error' ? 'error' : 'log'](`${prefix} ${message}`);
console.error(`${prefix} ${message}`);
} else {
console.log(`${prefix} ${message}`);
}
} }
// Start the TCP server
public start(): void { public start(): void {
const tcpServer = net.createServer(); const tcpServer = net.createServer();
// Handle incoming connections
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;
@@ -49,61 +43,66 @@ export class TcpServer {
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);
// Generate RSA key pair and start key exchange
tcpCommunicator.generateKeyPair(); tcpCommunicator.generateKeyPair();
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(); // Close the connection in case of any error socket.end();
}); });
// Handle incoming data in chunks this.clientQueues.set(clientId, Promise.resolve());
socket.on('data', async (data: Buffer) => {
this.log(`Data received from client ${clientId}`); socket.on('data', (data: Buffer) => {
await this.handleData(data, ip, port); this.queueClientDataProcessing(data, ip, port);
}); });
// Handle client disconnect
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);
}); });
// Handle socket errors
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);
}); });
}); });
// Handle server errors
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');
}); });
// Start listening for connections
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}`);
}); });
} }
// Handle incoming data from a client private queueClientDataProcessing(data: Buffer, ip: string, port: number): void {
const clientId = `${ip}:${port}`;
const clientQueue = this.clientQueues.get(clientId) || Promise.resolve();
this.clientQueues.set(
clientId,
clientQueue.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}`;
// Retrieve the communicator associated with this connection
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;
} }
// Handle incoming chunked message via communicator
await communicator.handleIncomingChunk(data); await communicator.handleIncomingChunk(data);
// Fetch and process result if available // Check if message is complete before fetching result
const handlerResult = communicator.getHandlerResult(); const handlerResult = communicator.getHandlerResult();
if (handlerResult) { if (handlerResult) {
try { try {
+1 -1
View File
@@ -50,7 +50,7 @@ export class UdpServer {
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
communicator.handleIncomingMessage(msg.toString()); await communicator.handleIncomingMessage(msg.toString());
const communicatorResult = communicator.getHandlerResult(); const communicatorResult = communicator.getHandlerResult();
if (communicatorResult) { if (communicatorResult) {
+1205 -1064
View File
File diff suppressed because it is too large Load Diff
+8 -2
View File
@@ -15,6 +15,7 @@
"author": "Cerbu Andrei - Mihnea", "author": "Cerbu Andrei - Mihnea",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"check-disk-space": "^3.4.0",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"ping": "^0.4.4", "ping": "^0.4.4",
"uuid": "^10.0.0" "uuid": "^10.0.0"
@@ -30,6 +31,7 @@
"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",
"typescript": "^5.6.2" "typescript": "^5.6.2"
}, },
"config": { "config": {
@@ -54,14 +56,18 @@
}, },
{ {
"name": "@electron-forge/maker-zip", "name": "@electron-forge/maker-zip",
"platforms": ["darwin"], "platforms": [
"darwin"
],
"config": { "config": {
"icon": "../app_icons/icon.icns" "icon": "../app_icons/icon.icns"
} }
}, },
{ {
"name": "@electron-forge/maker-deb", "name": "@electron-forge/maker-deb",
"platforms": ["linux"], "platforms": [
"linux"
],
"config": { "config": {
"icon": "../app_icons/icon.png" "icon": "../app_icons/icon.png"
} }
+9 -6
View File
@@ -87,11 +87,10 @@ export class DepartmentSharer {
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;
const clearSuccess = await this.clearDepartmentDirectory(); if (await this.clearDepartmentDirectory(userName)) {
if (clearSuccess) {
await this.sendFilesToUser(departmentFiles.structure, userName); await this.sendFilesToUser(departmentFiles.structure, userName);
} }
@@ -108,12 +107,14 @@ export class DepartmentSharer {
} }
// Clear the department directory for a user // Clear the department directory for a user
private async clearDepartmentDirectory(): 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)) return false; if (!await this.tcpCommunicator.sendMessage(operationCodes.CLEAR_DEPARTMENT, {userName: userName})) return false;
const response = await this.waitForResponse(); const response = await this.waitForResponse();
this.log(`${response?.operationCode}: ${response?.metaInfo?.message}`);
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;
@@ -158,6 +159,8 @@ export class DepartmentSharer {
return; return;
} }
this.log(`File sent successfully: ${fileName} to ${userName}`);
unsentFiles.splice(unsentFiles.indexOf(fileName), 1); unsentFiles.splice(unsentFiles.indexOf(fileName), 1);
await this.tcpCommunicator.disconnect(); await this.tcpCommunicator.disconnect();
} }
+32 -18
View File
@@ -10,6 +10,17 @@ export class WindowManager {
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.');
}
// Logging helper function
private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[WindowManager]';
if (level === 'error') {
console.error(`${prefix} ${message}`);
} else {
console.log(`${prefix} ${message}`);
}
} }
// Show an alert dialog // Show an alert dialog
@@ -21,8 +32,9 @@ export class WindowManager {
message: message, message: message,
buttons: ['OK'], buttons: ['OK'],
}); });
this.log(`Alert displayed with message: "${message}"`);
} else { } else {
console.error('Main window is not available.'); this.log('Main window is not available.', 'error');
} }
} }
@@ -31,17 +43,17 @@ export class WindowManager {
if (this.mainWindow) { if (this.mainWindow) {
try { try {
const destinationPath = path.join(this.pathToPagesDir, `${destination}.html`); const destinationPath = path.join(this.pathToPagesDir, `${destination}.html`);
console.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);
console.log(`Navigated to ${destination}`); this.log(`Navigated to ${destination}`);
} catch (error) { } catch (error) {
console.error('Error changing content:', 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 {
console.error('Main window is not available.'); this.log('Main window is not available.', 'error');
} }
} }
@@ -51,26 +63,26 @@ export class WindowManager {
properties: ['openDirectory'], // Only allow selecting directories properties: ['openDirectory'], // Only allow selecting directories
}); });
// If the user cancels, result.filePaths will be an empty array
if (result.filePaths && result.filePaths.length > 0) { if (result.filePaths && result.filePaths.length > 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 {
console.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
} }
} }
// Show a file in the explorer
async showFileInExplorer(filePath: string): Promise<void> { async showFileInExplorer(filePath: string): Promise<void> {
if (filePath && fs.existsSync(filePath)) { if (filePath && fs.existsSync(filePath)) {
try { try {
// Use Electron's shell module to show the file in the explorer
shell.showItemInFolder(filePath); shell.showItemInFolder(filePath);
console.log(`Opened file explorer for: ${filePath}`); this.log(`Opened file explorer for: ${filePath}`);
} catch (error: any) { } catch (error: any) {
console.error(`Error showing file in explorer: ${error.message}`); this.log(`Error showing file in explorer: ${error.message}`, 'error');
} }
} else { } else {
console.error('File path is undefined or does not exist.'); this.log('File path is undefined or does not exist.', 'error');
} }
} }
@@ -84,9 +96,10 @@ export class WindowManager {
}); });
if (result.filePaths && result.filePaths.length > 0) { if (result.filePaths && result.filePaths.length > 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 {
console.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
} }
} }
@@ -94,15 +107,14 @@ export class WindowManager {
// 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) {
// If the window is already open, focus it
this.announcementWindow.focus(); this.announcementWindow.focus();
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;
// Initialize the announcement window
this.announcementWindow = new BrowserWindow({ this.announcementWindow = new BrowserWindow({
width: width / 3, width: width / 3,
height: height / 2, height: height / 2,
@@ -116,20 +128,22 @@ export class WindowManager {
}); });
this.announcementWindow.removeMenu(); this.announcementWindow.removeMenu();
// Load the announcement page
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}`);
// Handle window close // Handle window close
this.announcementWindow.on('closed', () => { this.announcementWindow.on('closed', () => {
this.announcementWindow = null; // Clean up the reference this.announcementWindow = null;
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.');
} }
} }
} }
@@ -1,40 +0,0 @@
import { ParsedMessage } from '../message_handler';
import { OperationHandler } from './operation_handler';
import { OperationPlugin } from './operation_plugin';
export abstract class OperationBase implements OperationPlugin {
// Shared operation codes
public static readonly operationCodes = {
OK: 'OK',
ERR: 'ERR',
END: 'END',
};
// Default handler for OK operation
public static handleOk(parsedMessage: ParsedMessage): ParsedMessage {
return parsedMessage; // Typically, you would just acknowledge with OK, returning as-is
}
// Default handler for ERR operation
public static handleErr(parsedMessage: ParsedMessage): ParsedMessage {
return parsedMessage; // Typically, you would log the error and return
}
// Default handler for END operation
public static handleEnd(parsedMessage: ParsedMessage): ParsedMessage {
return {
operationCode: OperationBase.operationCodes.END,
metaInfo: { message: 'Connection ended.' },
};
}
// Register the common OK, ERR, and END handlers
public static registerCommonOperations(operationHandler: OperationHandler): void {
operationHandler.registerHandler(OperationBase.operationCodes.OK, OperationBase.handleOk);
operationHandler.registerHandler(OperationBase.operationCodes.ERR, OperationBase.handleErr);
operationHandler.registerHandler(OperationBase.operationCodes.END, OperationBase.handleEnd);
}
// Abstract register method that will be implemented by subclasses
public abstract register(operationHandler: OperationHandler): void;
}
@@ -2,14 +2,14 @@
import { ParsedMessage, MessageHandler } from '../message_handler'; import { ParsedMessage, MessageHandler } from '../message_handler';
import { OperationPlugin } from './operation_plugin'; import { OperationPlugin } from './operation_plugin';
type OperationHandlerFunction = (parsedMessage: ParsedMessage) => ParsedMessage; // Define handler function type to return 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() {
// Register only the unknown command handler on initialization
this.registerHandler('UNKNOWN_COMMAND', this.handleUnknownCommand); this.registerHandler('UNKNOWN_COMMAND', this.handleUnknownCommand);
} }
@@ -26,25 +26,24 @@ export class OperationHandler {
this.handlers[operationCode] = handler; this.handlers[operationCode] = handler;
} }
// Handle operation request // Handle operation request asynchronously
public handleOperation(rawMessage: string): ParsedMessage { public async handleOperation(rawMessage: string): Promise<ParsedMessage> {
// Parse and validate the message
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);
} }
// Dispatch the handler for the given operation code // 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 handler(parsedMessage); return await handler(parsedMessage);
} else { } else {
return this.handleUnknownCommand(parsedMessage); return this.handleUnknownCommand(parsedMessage);
} }
} }
// Default handler for unknown commands // Default handler for unknown commands
private handleUnknownCommand(parsedMessage: ParsedMessage): ParsedMessage { private async handleUnknownCommand(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
return { return {
operationCode: 'UNKNOWN_COMMAND', operationCode: 'UNKNOWN_COMMAND',
metaInfo: { message: 'Unknown command received.' }, metaInfo: { message: 'Unknown command received.' },
@@ -1,25 +1,26 @@
import { ParsedMessage } from '../message_handler'; import { ParsedMessage } from '../message_handler';
import { OperationBase } from '../operations_base/operation_base';
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";
export class GeneralOperations extends OperationBase { export class GeneralOperations implements OperationPlugin {
public static readonly operationCodes = { public static readonly operationCodes = {
...OperationBase.operationCodes, // Inherit common operation codes (OK, ERR, END) OK: 'OK',
ERR: 'ERR',
END: 'END',
HEARTBEAT: 'HEARTBEAT', HEARTBEAT: 'HEARTBEAT',
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 // Handle heartbeat operation asynchronously
public static handleHeartbeat(): ParsedMessage { public static async handleHeartbeat(): 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)) {
// @ts-ignore 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;
@@ -34,8 +35,8 @@ export class GeneralOperations extends OperationBase {
}; };
} }
// Handle public key exchange // Handle public key exchange asynchronously
public static handlePublicKey(parsedMessage: ParsedMessage): 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 {
@@ -50,14 +51,14 @@ export class GeneralOperations extends OperationBase {
} }
} }
// Handle AES key exchange // Handle AES key exchange asynchronously
public static handleAESKey(parsedMessage: ParsedMessage): 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: aesKey, aesIv: aesIv }, metaInfo: { aesKey, aesIv },
}; };
} else { } else {
return { return {
@@ -67,14 +68,22 @@ export class GeneralOperations extends OperationBase {
} }
} }
// Default async handler for OK operation
public static async handleOk(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
return parsedMessage; // Acknowledge with OK, returning as-is
}
// Default async handler for ERR operation
public static async handleErr(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
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 {
// Register specific handlers for the general operations
operationHandler.registerHandler(GeneralOperations.operationCodes.HEARTBEAT, GeneralOperations.handleHeartbeat); operationHandler.registerHandler(GeneralOperations.operationCodes.HEARTBEAT, GeneralOperations.handleHeartbeat);
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_PUBLIC_KEY, GeneralOperations.handlePublicKey); operationHandler.registerHandler(GeneralOperations.operationCodes.SET_PUBLIC_KEY, GeneralOperations.handlePublicKey);
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_AES_KEY, GeneralOperations.handleAESKey); // Register AES key handler operationHandler.registerHandler(GeneralOperations.operationCodes.SET_AES_KEY, GeneralOperations.handleAESKey);
operationHandler.registerHandler(GeneralOperations.operationCodes.OK, GeneralOperations.handleOk);
// Register common operations inherited from the base class operationHandler.registerHandler(GeneralOperations.operationCodes.ERR, GeneralOperations.handleErr);
OperationBase.registerCommonOperations(operationHandler);
} }
} }
@@ -1,18 +1,16 @@
import { ParsedMessage } from '../message_handler'; import { ParsedMessage } from '../message_handler';
import { OperationBase } from '../operations_base/operation_base';
import { OperationHandler } from '../operations_base/operation_handler'; import { OperationHandler } from '../operations_base/operation_handler';
import {operationCodes} from "../operation_codes";
import path from 'path'; import path from 'path';
import { execSync } from 'child_process'; import fs from 'fs/promises';
import fs from 'fs'; import checkDiskSpace from "check-disk-space";
import { JsonManager } from '../../helpers/json_manager';
import {OperationPlugin} from "../operations_base/operation_plugin";
const LOCK_FILE_EXTENSION = '.lock'; export class UserToUserOperations implements OperationPlugin {
export class UserToUserOperations extends OperationBase {
public static readonly operationCodes = { public static readonly operationCodes = {
...OperationBase.operationCodes, // Inherit common operation codes (OK, ERR, END)
SEND_ANNOUNCEMENT: 'SEND_ANNOUNCEMENT', SEND_ANNOUNCEMENT: 'SEND_ANNOUNCEMENT',
GET_USER_INFORMATION: 'GET_USER_INFORMATION', GET_USER_INFORMATION: 'GET_USER_INFORMATION',
RESET_DATABASE: 'RESET_DATABASE',
BACKUP_FILE: 'BACKUP_FILE', BACKUP_FILE: 'BACKUP_FILE',
CLEAR_BACKUP: 'CLEAR_BACKUP', CLEAR_BACKUP: 'CLEAR_BACKUP',
SHARE_FILE: 'SHARE_FILE', SHARE_FILE: 'SHARE_FILE',
@@ -24,429 +22,149 @@ export class UserToUserOperations extends OperationBase {
DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE', DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE',
}; };
// Utility function to pause execution (sleep) private static async hasEnoughDiskSpace(directory: string, requiredPercentage: number = 25): Promise<boolean> {
static sleep(ms: number): void {
const start = Date.now();
while (Date.now() - start < ms) {
// Busy wait loop (not optimal but fine for this short duration)
}
}
// Read JSON file with a lock mechanism
static readJsonSync(filePath: string): any {
const lockFilePath = `${filePath}.${LOCK_FILE_EXTENSION}`;
const absolutePath = path.resolve(filePath);
try { try {
// Loop until the lock file is removed by another process const diskInfo = await checkDiskSpace(directory);
while (fs.existsSync(lockFilePath)) { const availableSpace = diskInfo.free;
console.log(`Waiting for lock file to be released: ${lockFilePath}`); const totalSpace = diskInfo.size;
UserToUserOperations.sleep(100);
}
// Create lock file to signal this process is working on the file
fs.writeFileSync(lockFilePath, ''); // Create the lock file
// Read and parse the JSON file
const fileContents = fs.readFileSync(absolutePath, 'utf-8');
const parsedJson = JSON.parse(fileContents);
// Once processing is done, delete the lock file
fs.unlinkSync(lockFilePath); // Remove the lock file
return parsedJson; // Return the parsed JSON data
} catch (error) {
console.error(`Error reading or parsing JSON from ${filePath}:`, error);
// Ensure the lock file is removed even in case of an error
if (fs.existsSync(lockFilePath)) {
fs.unlinkSync(lockFilePath);
}
return null; // Return null or throw error based on preference
}
}
static writeJsonSync(filePath: string, data: any): boolean {
const lockFilePath = `${filePath}.${LOCK_FILE_EXTENSION}`;
const absolutePath = path.resolve(filePath);
try {
// Loop until the lock file is removed by another process
while (fs.existsSync(lockFilePath)) {
console.log(`Waiting for lock file to be released: ${lockFilePath}`);
UserToUserOperations.sleep(100); // Use the sleep utility to pause
}
// Create lock file to signal this process is working on the file
fs.writeFileSync(lockFilePath, ''); // Create the lock file
// Write the data to the JSON file
fs.writeFileSync(absolutePath, JSON.stringify(data, null, 2), 'utf-8');
console.log(`Data written successfully to ${absolutePath}`);
// Once processing is done, delete the lock file
fs.unlinkSync(lockFilePath); // Remove the lock file
return true; // Indicate successful write
} catch (error) {
console.error(`Error writing JSON to ${filePath}:`, error);
// Ensure the lock file is removed even in case of an error
if (fs.existsSync(lockFilePath)) {
fs.unlinkSync(lockFilePath);
}
return false; // Indicate failure
}
}
private static hasEnoughDiskSpace(directory: string, requiredPercentage: number): boolean {
try {
let availableSpace = 0;
let totalSpace = 0;
if (process.platform === 'win32') {
// Windows
const output = execSync(`wmic logicaldisk where "DeviceID='${directory[0]}:'" get FreeSpace,Size`).toString();
const lines = output.trim().split('\n');
const [freeSpaceStr, totalSpaceStr] = lines[1].trim().split(/\s+/);
availableSpace = parseInt(freeSpaceStr, 10); // Available space in bytes
totalSpace = parseInt(totalSpaceStr, 10); // Total space in bytes
} else {
// Unix-based (Linux/macOS)
const output = execSync(`df -k "${directory}"`).toString();
const lines = output.trim().split('\n');
const parts = lines[lines.length - 1].split(/\s+/);
const availableSpaceInKb = parseInt(parts[3], 10); // Available space in KB
const totalSpaceInKb = parseInt(parts[1], 10); // Total space in KB
availableSpace = availableSpaceInKb * 1024;
totalSpace = totalSpaceInKb * 1024;
}
// Calculate available space as a percentage of the total space
const availablePercentage = (availableSpace / totalSpace) * 100; const availablePercentage = (availableSpace / totalSpace) * 100;
// Return true if the available percentage is greater than or equal to the required percentage
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 if there's an error return false;
} }
} }
public static handleResetDatabase(parsedMessage: ParsedMessage): ParsedMessage {
// Path to the application.json file
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
public static async handleSendAnnouncement(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
if (!parsedMessage.metaInfo?.message) {
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing announcement message.' }};
}
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json'));
try { try {
// Read the existing data from application.json await jsonManager.writeValue('announcement', parsedMessage.metaInfo.message);
const appData = UserToUserOperations.readJsonSync(pathToApplicationJson) || {}; console.log(`Announcement saved: ${parsedMessage.metaInfo.message}`);
return { operationCode: operationCodes.OK, metaInfo: { message: 'Announcement saved.' }};
// Update the reset_application_preferences field to true
appData.reset_application_preferences = true;
// Write the updated data back to application.json
const success = UserToUserOperations.writeJsonSync(pathToApplicationJson, appData);
if (success) {
console.log(`Application preferences reset successfully.`);
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { message: 'Application preferences reset successfully.' },
};
} else {
throw new Error("Failed to write to application.json");
}
} catch (error: any) { } catch (error: any) {
console.error(`Error resetting application preferences: ${error.message}`); console.error(`Error saving announcement: ${error.message}`);
return { return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` }};
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error resetting application preferences: ${error.message}` },
};
} }
} }
public static async handleGetUserInformation(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
public static handleSendAnnouncement(parsedMessage: ParsedMessage): ParsedMessage { const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'userConfig.json'));
// Ensure the message is available in metaInfo
if (!parsedMessage.metaInfo || !parsedMessage.metaInfo.message) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing announcement message in meta information.' },
};
}
const announcementMessage = parsedMessage.metaInfo.message;
// Path to the application.json file
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
try { try {
// Read the existing data from application.json const userInfo = await jsonManager.readValue('user_info');
const appData = UserToUserOperations.readJsonSync(pathToApplicationJson) || {}; return { operationCode: operationCodes.OK, metaInfo: userInfo };
// Update the announcement field with the new message
appData.announcement = announcementMessage;
// Write the updated data back to application.json
fs.writeFileSync(pathToApplicationJson, JSON.stringify(appData, null, 2), 'utf-8');
console.log(`Announcement message saved successfully: ${announcementMessage}`);
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { message: 'Announcement message saved successfully.' },
};
} catch (error: any) { } catch (error: any) {
console.error(`Error saving announcement message: ${error.message}`); console.error(`Error fetching user info: ${error.message}`);
return { return { operationCode: operationCodes.ERR, metaInfo: { message: 'Error fetching user info' }};
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error saving announcement message: ${error.message}` },
};
} }
} }
// Handle user information retrieval public static async handleBackupFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
public static handleGetUserInformation(parsedMessage: ParsedMessage): ParsedMessage { if (!parsedMessage.metaInfo?.userName || !parsedMessage.metaInfo?.relativeFilePath || !parsedMessage.fileContent) {
const pathToUserJson = path.join(__dirname, '..', '..', 'json_files', 'userConfig.json'); return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing file or user information.' }};
const userInfo = UserToUserOperations.readJsonSync(pathToUserJson);
if (userInfo) {
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: userInfo.user_info,
};
} else {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Error fetching user information.' },
};
}
}
// Handle file reception and saving
public static handleBackupFile(parsedMessage: ParsedMessage): ParsedMessage {
// Ensure metaInfo and fileContent are available
if (!parsedMessage.metaInfo || !parsedMessage.fileContent) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing file content or meta information.' },
};
} }
const { userName, relativeFilePath } = parsedMessage.metaInfo; const { userName, relativeFilePath } = parsedMessage.metaInfo;
const fullFilePath = path.join(__dirname, '..', '..', 'backups', userName, relativeFilePath);
if (!userName || !relativeFilePath) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing user name or file path information.' },
};
}
// Base directory where backups will be stored
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
// Full path where the file will be stored (under the user's directory)
const fullFilePath = path.join(baseBackupDir, userName, relativeFilePath);
try { try {
// Check if there is enough disk space if (!await UserToUserOperations.hasEnoughDiskSpace(path.dirname(fullFilePath), 25)) {
const requiredPercentage = 25; return { operationCode: operationCodes.ERR, metaInfo:
if (!UserToUserOperations.hasEnoughDiskSpace(baseBackupDir, requiredPercentage)) { { message: 'Insufficient disk space.' }};
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Insufficient disk space for backup.' },
};
} }
// Ensure the directory structure exists (create directories if they don't exist) await fs.mkdir(path.dirname(fullFilePath), { recursive: true });
const dirPath = path.dirname(fullFilePath); await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer);
if (!fs.existsSync(dirPath)) { console.log(`File saved: ${fullFilePath}`);
fs.mkdirSync(dirPath, { recursive: true });
}
// Write the file content to the correct path return { operationCode: operationCodes.OK, metaInfo: { message: `File saved: ${relativeFilePath}` }};
fs.writeFileSync(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64');
console.log(`File saved successfully: ${fullFilePath}`);
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { message: `File saved successfully: ${relativeFilePath}` },
};
} catch (error: any) { } catch (error: any) {
console.error(`Error saving file: ${error.message}`); console.error(`Error saving file: ${error.message}`);
return { return { operationCode: operationCodes.ERR, metaInfo: { message: `Error saving file: ${error.message}` }};
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error saving file: ${error.message}` },
};
} }
} }
// Handle clearing all backups for a user public static async handleClearBackup(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
public static handleClearBackup(parsedMessage: ParsedMessage): ParsedMessage { if (!parsedMessage.metaInfo?.userName) {
// Ensure the userName is available in metaInfo return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' }};
if (!parsedMessage.metaInfo || !parsedMessage.metaInfo.userName) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing user name in meta information.' },
};
} }
const { userName } = parsedMessage.metaInfo; const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.userName);
// Base directory where backups are stored
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
const userBackupDir = path.join(baseBackupDir, userName);
try { try {
// Check if the user's backup directory exists await fs.rm(userBackupDir, { recursive: true, force: true });
if (fs.existsSync(userBackupDir)) { console.log(`Backup cleared: ${userBackupDir}`);
// Recursively delete the user's backup directory return { operationCode: operationCodes.OK, metaInfo: { message: `Backup cleared for ${parsedMessage.metaInfo.userName}` }};
fs.rmSync(userBackupDir, { recursive: true, force: true });
console.log(`Backup cleared successfully for user: ${userName}`);
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { message: `Backup cleared successfully for user: ${userName}` },
};
} else {
console.warn(`Backup directory not found for user: ${userName}`);
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Backup directory not found for user: ${userName}` },
};
}
} catch (error: any) { } catch (error: any) {
console.error(`Error clearing backup for user: ${userName} - ${error.message}`); console.error(`Error clearing backup: ${error.message}`);
return { return { operationCode: operationCodes.ERR, metaInfo: { message: `Error clearing backup: ${error.message}` }};
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error clearing backup for user: ${userName}` },
};
} }
} }
// Handle sharing file operation public static async handleShareFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
public static handleShareFile(parsedMessage: ParsedMessage): ParsedMessage { if (!parsedMessage.metaInfo?.userName || !parsedMessage.metaInfo?.relativeFilePath || !parsedMessage.fileContent) {
// Ensure metaInfo and fileContent are available return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing share info.' }};
if (!parsedMessage.metaInfo || !parsedMessage.fileContent) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing file content or meta information.' },
};
} }
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json'));
const { userName, relativeFilePath } = parsedMessage.metaInfo; const { userName, relativeFilePath } = parsedMessage.metaInfo;
if (!userName || !relativeFilePath) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing user name or file path information.' },
};
}
// Path to the application.json to read the shareDirectory field
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
const appInfo = UserToUserOperations.readJsonSync(pathToApplicationJson);
// Corrected the condition
if (!appInfo || !appInfo.shareDirectory) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Error retrieving share directory from application.json.' },
};
}
// Get the share directory path
const shareDirectory = appInfo.shareDirectory.path;
// Full path where the file will be stored (under the user's directory in the shared folder)
const fullFilePath = path.join(shareDirectory, userName, relativeFilePath);
try { try {
// Ensure the directory structure exists (create directories if they don't exist) const appInfo = await jsonManager.readValue('shareDirectory');
const dirPath = path.dirname(fullFilePath); const shareDirectory = appInfo?.path || '';
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true }); if (!shareDirectory) {
return { operationCode: operationCodes.ERR, metaInfo: { message: 'Share directory missing.' }};
} }
// Write the file content to the correct path const fullFilePath = path.join(shareDirectory, userName, relativeFilePath);
fs.writeFileSync(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64'); await fs.mkdir(path.dirname(fullFilePath), { recursive: true });
await fs.writeFile(fullFilePath, parsedMessage.fileContent as Buffer);
console.log(`File shared: ${fullFilePath}`);
console.log(`File shared successfully: ${fullFilePath}`); return { operationCode: operationCodes.OK, metaInfo: { message: `File shared: ${relativeFilePath}` }};
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { message: `File shared successfully: ${relativeFilePath}` },
};
} catch (error: any) { } catch (error: any) {
console.error(`Error sharing file: ${error.message}`); console.error(`Error sharing file: ${error.message}`);
return { return { operationCode: operationCodes.ERR, metaInfo: { message: `Error sharing file: ${error.message}` }};
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error sharing file: ${error.message}` },
};
} }
} }
public static handleClearDepartment(parsedMessage: ParsedMessage): ParsedMessage { public static async handleClearDepartment(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
// Ensure the userName is available in metaInfo if (!parsedMessage.metaInfo?.userName) {
if (!parsedMessage.metaInfo || !parsedMessage.metaInfo.userName) { return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing user name.' }};
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing user name in meta information.' },
};
} }
const jsonManager = new JsonManager(path.join(__dirname, '..', '..', 'json_files', 'application.json'));
const { userName } = parsedMessage.metaInfo; const { userName } = parsedMessage.metaInfo;
const pathToApplicationJson = path.join(__dirname, '..', '..', 'json_files', 'application.json');
const appInfo = UserToUserOperations.readJsonSync(pathToApplicationJson);
// Corrected the condition
if (!appInfo || !appInfo.departmentDirectory) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Error retrieving share directory from application.json.' },
};
}
// Get the share directory path
const baseDepartmentDir = appInfo.departmentDirectory.path;
const userDepartmentDir = path.join(baseDepartmentDir, '..', 'DEPARTMENT_FILES', userName);
try { try {
// Check if the user's backup directory exists const appInfo = await jsonManager.readValue('departmentDirectory');
if (fs.existsSync(userDepartmentDir)) { const departmentDir = appInfo?.path || '';
// Recursively delete the user's backup directory const userDepartmentDir = path.join(departmentDir, '..', 'DEPARTMENT_SHARE', userName);
fs.rmSync(userDepartmentDir, { recursive: true, force: true });
console.log(`Backup cleared successfully for user: ${userName}`);
return { try {
operationCode: UserToUserOperations.operationCodes.OK, await fs.access(userDepartmentDir)
metaInfo: { message: `Backup cleared successfully for user: ${userName}`}, }catch(ex: any){
}; return { operationCode: operationCodes.OK};
} else {
console.warn(`Backup directory not found for user: ${userName}`);
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Backup directory not found for user: ${userName}`},
};
} }
await fs.rm(userDepartmentDir, { recursive: true, force: true });
console.log(`Department backup cleared: ${userDepartmentDir}`);
return { operationCode: operationCodes.OK, metaInfo: { message: `Department backup cleared for ${userName}` }};
} catch (error: any) { } catch (error: any) {
console.error(`Error clearing backup for user: ${userName} - ${error.message}`); console.error(`Error clearing department: ${error.message}`);
return { return { operationCode: operationCodes.ERR, metaInfo: { message: `Error clearing department: ${error.message}` }};
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error clearing backup for user: ${userName}`},
};
} }
} }
public static handleDepartmentFile(parsedMessage: ParsedMessage): ParsedMessage { public static async handleDepartmentFile(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
// Ensure metaInfo and fileContent are available // Ensure metaInfo and fileContent are available
if (!parsedMessage.metaInfo || !parsedMessage.fileContent) { if (!parsedMessage.metaInfo || !parsedMessage.fileContent) {
return { return {
operationCode: UserToUserOperations.operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: 'Missing file content or meta information.' }, metaInfo: { message: 'Missing file content or meta information.' },
}; };
} }
@@ -455,130 +173,99 @@ export class UserToUserOperations extends OperationBase {
if (!userName || !relativeFilePath) { if (!userName || !relativeFilePath) {
return { return {
operationCode: UserToUserOperations.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 appInfo = UserToUserOperations.readJsonSync(pathToApplicationJson); const jsonManager = new JsonManager(pathToApplicationJson);
// Corrected the condition // Read application configuration asynchronously
if (!appInfo || !appInfo.departmentDirectory) { let appInfo;
try {
appInfo = await jsonManager.readValue('departmentDirectory');
} catch (error: any) {
return { return {
operationCode: UserToUserOperations.operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: 'Error retrieving share directory from application.json.' }, metaInfo: { message: `Error reading application config: ${error.message}` },
};
}
if (!appInfo || !appInfo.path) {
return {
operationCode: operationCodes.ERR,
metaInfo: { message: 'Error retrieving department directory from application.json.' },
}; };
} }
// Get the share directory path // Get the share directory path
const departmentDirectory = appInfo.departmentDirectory.path; const departmentDirectory = appInfo.path;
// Full path where the file will be stored (under the user's directory in the shared folder)
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);
if (!fs.existsSync(dirPath)) { await fs.mkdir(dirPath, { recursive: true });
fs.mkdirSync(dirPath, { recursive: true });
}
// Write the file content to the correct path // Write the file content to the specified path
fs.writeFileSync(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64'); await fs.writeFile(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64');
console.log(`File shared successfully: ${fullFilePath}`);
return { return {
operationCode: UserToUserOperations.operationCodes.OK, operationCode: operationCodes.OK,
metaInfo: { message: `File shared successfully: ${relativeFilePath}`}, metaInfo: { message: `File shared successfully: ${relativeFilePath}` },
}; };
} catch (error: any) { } catch (error: any) {
console.error(`Error sharing file: ${error.message}`);
return { return {
operationCode: UserToUserOperations.operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: {message: `Error sharing file: ${error.message}`}, metaInfo: { message: `Error sharing file: ${error.message}` },
}; };
} }
} }
// Check if a backup has been created for a user public static async handleIsBackupCreated(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
public static handleIsBackupCreated(parsedMessage: ParsedMessage): ParsedMessage { if (!parsedMessage.metaInfo?.name) {
if(!parsedMessage.metaInfo) { return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing name in meta information.' }};
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing metaInfo.' },
};
} }
const {name} = parsedMessage.metaInfo; const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name);
if (!name) {
return { try {
operationCode: UserToUserOperations.operationCodes.ERR, const exists = await fs.access(userBackupDir).then(() => true).catch(() => false);
metaInfo: { message: 'Missing name in meta information.' }, return { operationCode: operationCodes.OK, metaInfo: { backupExists: exists }};
}; } catch (error: any) {
console.error(`Error checking backup: ${error.message}`);
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error checking backup: ${error.message}` }};
} }
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
const userBackupDir = path.join(baseBackupDir, name);
console.log(userBackupDir);
const exists = fs.existsSync(userBackupDir);
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { backupExists: exists },
};
} }
// Get the structure of the backup directory for a user public static async handleGetBackupStructure(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
public static handleGetBackupStructure(parsedMessage: ParsedMessage): ParsedMessage { if (!parsedMessage.metaInfo?.name) {
if(!parsedMessage.metaInfo) { return { operationCode: operationCodes.ERR, metaInfo: { message: 'Missing name in meta information.' }};
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing metaInfo.' },
};
} }
const {name} = parsedMessage.metaInfo; const userBackupDir = path.join(__dirname, '..', '..', 'backups', parsedMessage.metaInfo.name);
if (!name) {
return { try {
operationCode: UserToUserOperations.operationCodes.ERR, const structure = await UserToUserOperations.buildDirectoryStructure(userBackupDir);
metaInfo: { message: 'Missing name in meta information.' }, return { operationCode: operationCodes.OK, metaInfo: { structure }};
}; } catch (error: any) {
console.error(`Error building backup structure: ${error.message}`);
return { operationCode: operationCodes.ERR, metaInfo: { message: `Error: ${error.message}` }};
} }
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
const userBackupDir = path.join(baseBackupDir, name);
if (!fs.existsSync(userBackupDir)) {
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { structure: {} }, // Return empty structure if directory doesn't exist
};
}
const fileStructure = UserToUserOperations.buildDirectoryStructure(userBackupDir);
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { structure: fileStructure },
};
} }
// Build the directory structure recursively private static async buildDirectoryStructure(directoryPath: string): Promise<any> {
private static buildDirectoryStructure(directoryPath: string): any {
const structure: any = {}; const structure: any = {};
const files = fs.readdirSync(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 = fs.statSync(filePath); const stats = await fs.stat(filePath);
if (stats.isDirectory()) { if (stats.isDirectory()) {
structure[file] = UserToUserOperations.buildDirectoryStructure(filePath); // Recursive for subdirectories structure[file] = await UserToUserOperations.buildDirectoryStructure(filePath);
} else { } else {
structure[file] = path.relative(directoryPath, filePath); structure[file] = path.relative(directoryPath, filePath);
} }
@@ -587,67 +274,43 @@ export class UserToUserOperations extends OperationBase {
return structure; return structure;
} }
// Handle file request from backup directory public static async handleReqFileFromBackup(parsedMessage: ParsedMessage): Promise<ParsedMessage> {
public static handleReqFileFromBackup(parsedMessage: ParsedMessage): ParsedMessage { if (!parsedMessage.metaInfo?.name || !parsedMessage.metaInfo?.relativeFilePath) {
if(!parsedMessage.metaInfo) {
return { return {
operationCode: UserToUserOperations.operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: 'Missing metaInfo.' }, 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);
if (!name || !relativeFilePath) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing name or file path in meta information.' },
};
}
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
const userBackupDir = path.join(baseBackupDir, name);
const fullFilePath = path.join(userBackupDir, relativeFilePath);
if (!fs.existsSync(fullFilePath)) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'File not found in backup.' },
};
}
try { try {
const fileContent = fs.readFileSync(fullFilePath, 'base64'); const fileContent = await fs.readFile(fullFilePath);
return { return {
operationCode: UserToUserOperations.operationCodes.OK, operationCode: operationCodes.OK,
metaInfo: { relativeFilePath }, metaInfo: { relativeFilePath },
fileContent: Buffer.from(fileContent, 'base64') fileContent,
}; };
} catch (error: any) { } catch (error: any) {
console.error(`Error reading file: ${error.message}`); console.error(`Error reading file from backup: ${error.message}`);
return { return {
operationCode: UserToUserOperations.operationCodes.ERR, operationCode: operationCodes.ERR,
metaInfo: { message: `Error reading file: ${error.message}`}, metaInfo: { message: `Error reading file: ${error.message}` },
}; };
} }
} }
// Register user-to-user operations with the OperationHandler
public register(operationHandler: OperationHandler): void { public register(operationHandler: OperationHandler): void {
// Register specific handlers for user-to-user operations
operationHandler.registerHandler(UserToUserOperations.operationCodes.RESET_DATABASE, UserToUserOperations.handleResetDatabase);
operationHandler.registerHandler(UserToUserOperations.operationCodes.SEND_ANNOUNCEMENT, UserToUserOperations.handleSendAnnouncement); operationHandler.registerHandler(UserToUserOperations.operationCodes.SEND_ANNOUNCEMENT, UserToUserOperations.handleSendAnnouncement);
operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_USER_INFORMATION, UserToUserOperations.handleGetUserInformation); operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_USER_INFORMATION, UserToUserOperations.handleGetUserInformation);
operationHandler.registerHandler(UserToUserOperations.operationCodes.BACKUP_FILE, UserToUserOperations.handleBackupFile); operationHandler.registerHandler(UserToUserOperations.operationCodes.BACKUP_FILE, UserToUserOperations.handleBackupFile);
operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_BACKUP, UserToUserOperations.handleClearBackup); operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_BACKUP, UserToUserOperations.handleClearBackup);
operationHandler.registerHandler(UserToUserOperations.operationCodes.SHARE_FILE, UserToUserOperations.handleShareFile); operationHandler.registerHandler(UserToUserOperations.operationCodes.SHARE_FILE, UserToUserOperations.handleShareFile);
operationHandler.registerHandler(UserToUserOperations.operationCodes.DEPARTMENT_FILE, UserToUserOperations.handleDepartmentFile);
operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_DEPARTMENT, UserToUserOperations.handleClearDepartment); 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.IS_BACKUP_CREATED, UserToUserOperations.handleIsBackupCreated);
operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_BACKUP_STRUCTURE, UserToUserOperations.handleGetBackupStructure); operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_BACKUP_STRUCTURE, UserToUserOperations.handleGetBackupStructure);
operationHandler.registerHandler(UserToUserOperations.operationCodes.REQ_FILE_FROM_BACKUP, UserToUserOperations.handleReqFileFromBackup); operationHandler.registerHandler(UserToUserOperations.operationCodes.REQ_FILE_FROM_BACKUP, UserToUserOperations.handleReqFileFromBackup);
// Register common operations inherited from the base class
OperationBase.registerCommonOperations(operationHandler);
} }
} }
@@ -122,7 +122,7 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
for (let i = 0; i < messages.length - 1; i++) { for (let i = 0; i < messages.length - 1; i++) {
const completeMessage = messages[i]; const completeMessage = messages[i];
if (completeMessage) { if (completeMessage) {
this.processCompleteMessage(completeMessage); await this.processCompleteMessage(completeMessage);
} }
} }
@@ -130,7 +130,7 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
} }
} }
private processCompleteMessage(completeMessage: string): void { private async processCompleteMessage(completeMessage: string): Promise<void> {
const [headerJson, chunkContent] = completeMessage.split('|'); const [headerJson, chunkContent] = completeMessage.split('|');
const header = JSON.parse(headerJson); const header = JSON.parse(headerJson);
@@ -142,12 +142,12 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) { if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) {
const fullMessage = this.chunkBuffers[header.messageId].join(''); const fullMessage = this.chunkBuffers[header.messageId].join('');
this.handleIncomingMessage(fullMessage); await this.handleIncomingMessage(fullMessage);
delete this.chunkBuffers[header.messageId]; delete this.chunkBuffers[header.messageId];
} }
} }
handleIncomingMessage(incomingMessage: string): void { async handleIncomingMessage(incomingMessage: string): Promise<void> {
let messageToProcess = incomingMessage; let messageToProcess = incomingMessage;
if (this.aesKey && this.aesIv) { if (this.aesKey && this.aesIv) {
messageToProcess = this.decryptWithAes(incomingMessage); messageToProcess = this.decryptWithAes(incomingMessage);
@@ -155,7 +155,7 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
messageToProcess = this.decryptWithRsa(incomingMessage); messageToProcess = this.decryptWithRsa(incomingMessage);
} }
const result = 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;
@@ -176,6 +176,8 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
} }
getHandlerResult(): ParsedMessage | null { getHandlerResult(): ParsedMessage | null {
return this.handlerResult; const message = this.handlerResult;
this.handlerResult = null;
return message;
} }
} }
@@ -106,7 +106,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
for (let i = 0; i < messages.length - 1; i++) { for (let i = 0; i < messages.length - 1; i++) {
const completeMessage = messages[i]; const completeMessage = messages[i];
if (completeMessage) { if (completeMessage) {
this.processCompleteMessage(completeMessage); await this.processCompleteMessage(completeMessage);
} }
} }
@@ -114,7 +114,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
} }
} }
private processCompleteMessage(completeMessage: string): void { private async processCompleteMessage(completeMessage: string): Promise<void> {
const [headerJson, chunkContent] = completeMessage.split('|'); const [headerJson, chunkContent] = completeMessage.split('|');
const header = JSON.parse(headerJson); const header = JSON.parse(headerJson);
@@ -126,7 +126,7 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) { if (this.chunkBuffers[header.messageId].every((chunk) => chunk !== undefined)) {
const fullMessage = this.chunkBuffers[header.messageId].join(''); const fullMessage = this.chunkBuffers[header.messageId].join('');
this.handleIncomingMessage(fullMessage); await this.handleIncomingMessage(fullMessage);
delete this.chunkBuffers[header.messageId]; delete this.chunkBuffers[header.messageId];
} }
} }
@@ -169,14 +169,14 @@ export class TcpServerCommunicator extends SocketCommunicatorBase {
} }
// Handle incoming message (decrypt with AES if available) // Handle incoming message (decrypt with AES if available)
handleIncomingMessage(incomingMessage: string): void { async handleIncomingMessage(incomingMessage: string): Promise<void> {
let messageToProcess = incomingMessage; let messageToProcess = incomingMessage;
if (this.aesKey && this.aesIv) { if (this.aesKey && this.aesIv) {
messageToProcess = this.decryptWithAes(incomingMessage); messageToProcess = this.decryptWithAes(incomingMessage);
} }
this.handlerResult = this.operationHandler.handleOperation(messageToProcess); this.handlerResult = await this.operationHandler.handleOperation(messageToProcess);
} }
// Write message to socket // Write message to socket
@@ -12,8 +12,8 @@ export class UdpSocketCommunicator extends SocketCommunicatorBase {
} }
// Handle incoming message (no decryption needed for UDP) // Handle incoming message (no decryption needed for UDP)
handleIncomingMessage(incomingMessage: string): void { async handleIncomingMessage(incomingMessage: string): Promise<void> {
this.handlerResult = this.operationHandler.handleOperation(incomingMessage); this.handlerResult = await this.operationHandler.handleOperation(incomingMessage);
} }
// Send plain message over UDP (metaInfo as JSON and fileContent as Buffer) // Send plain message over UDP (metaInfo as JSON and fileContent as Buffer)
+23 -24
View File
@@ -14,6 +14,7 @@ export class TcpServer {
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();
constructor(host: string, port: number) { constructor(host: string, port: number) {
this.connectionManager = new ConnectionManager(); this.connectionManager = new ConnectionManager();
@@ -25,21 +26,14 @@ export class TcpServer {
this.operationHandler.loadPlugin(new UserToUserOperations()); this.operationHandler.loadPlugin(new UserToUserOperations());
} }
// Unified logging function
private log(message: string, level: 'log' | 'error' = 'log'): void { private log(message: string, level: 'log' | 'error' = 'log'): void {
const prefix = '[TcpServer]'; const prefix = '[TcpServer]';
if (level === 'error') { console[level === 'error' ? 'error' : 'log'](`${prefix} ${message}`);
console.error(`${prefix} ${message}`);
} else {
console.log(`${prefix} ${message}`);
}
} }
// Start the TCP server
public start(): void { public start(): void {
const tcpServer = net.createServer(); const tcpServer = net.createServer();
// Handle incoming connections
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;
@@ -49,61 +43,66 @@ export class TcpServer {
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);
// Generate RSA key pair and start key exchange
tcpCommunicator.generateKeyPair(); tcpCommunicator.generateKeyPair();
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(); // Close the connection in case of any error socket.end();
}); });
// Handle incoming data in chunks this.clientQueues.set(clientId, Promise.resolve());
socket.on('data', async (data: Buffer) => {
this.log(`Data received from client ${clientId}`); socket.on('data', (data: Buffer) => {
await this.handleData(data, ip, port); this.queueClientDataProcessing(data, ip, port);
}); });
// Handle client disconnect
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);
}); });
// Handle socket errors
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);
}); });
}); });
// Handle server errors
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');
}); });
// Start listening for connections
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}`);
}); });
} }
// Handle incoming data from a client private queueClientDataProcessing(data: Buffer, ip: string, port: number): void {
const clientId = `${ip}:${port}`;
const clientQueue = this.clientQueues.get(clientId) || Promise.resolve();
this.clientQueues.set(
clientId,
clientQueue.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}`;
// Retrieve the communicator associated with this connection
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;
} }
// Handle incoming chunked message via communicator
await communicator.handleIncomingChunk(data); await communicator.handleIncomingChunk(data);
// Fetch and process result if available // Check if message is complete before fetching result
const handlerResult = communicator.getHandlerResult(); const handlerResult = communicator.getHandlerResult();
if (handlerResult) { if (handlerResult) {
try { try {
+1 -1
View File
@@ -50,7 +50,7 @@ export class UdpServer {
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
communicator.handleIncomingMessage(msg.toString()); await communicator.handleIncomingMessage(msg.toString());
const communicatorResult = communicator.getHandlerResult(); const communicatorResult = communicator.getHandlerResult();
if (communicatorResult) { if (communicatorResult) {