BACKEND DONE FOR ALL APPS

This commit is contained in:
andrei-mihnea-cerbu
2024-10-21 18:34:45 +03:00
parent 4157ca9e7d
commit ab1eaec413
349 changed files with 17199 additions and 14015 deletions
+205
View File
@@ -0,0 +1,205 @@
import fs from 'fs';
import path from 'path';
import { FileEncryptor } from './file_encryptor'; // Assume the class is in this file
import { MemoryManager } from './memory_manager'; // Assume this handles memory-based storage
import { JsonManager } from './json_manager'; // Manages JSON-based configurations
import { TcpClient } from '../network/tcp/tcp_client' // Import your TcpClient class
import { operationCodes } from '../network/operation_codes';
import { parentPort } from 'worker_threads';
export class BackupManager {
private fileEncryptor: FileEncryptor | null = null;
private memoryManager: MemoryManager;
private applicationInfo: JsonManager;
private userConfig: JsonManager;
private readonly clientPort: number;
constructor(userConfigPath: string, applicationInfoPath: string, memoryManagerPath: string, clientPort: number) {
this.applicationInfo = new JsonManager(applicationInfoPath);
this.userConfig = new JsonManager(userConfigPath);
this.memoryManager = new MemoryManager(memoryManagerPath);
this.clientPort = clientPort;
}
async start(): Promise<void> {
setInterval(async () => {
await this.initialize(); // Re-run every minute
}, 60000); // 1 minute interval
}
// Initialize the backup process: fetch data from the app info and memory
private async initialize(): Promise<void> {
// Initialize the encryption settings from UserConfig
const encryptionKeyData = await this.userConfig.readValue('encryption_key');
if (!encryptionKeyData || !encryptionKeyData.key || !encryptionKeyData.iv) {
console.error('Encryption key data is missing in user configuration.');
return;
}
// Create a new FileEncryptor with the retrieved key and IV
this.fileEncryptor = new FileEncryptor(encryptionKeyData.key, encryptionKeyData.iv);
// Get the name of the user
const userInfo = await this.userConfig.readValue('user_info');
if (!userInfo || !userInfo.name) {
console.error('User information is missing in user configuration.');
return;
}
const userName = userInfo.name;
// Get the backup directory information from ApplicationInfo
const backupDirectoryData = await this.applicationInfo.readValue('backupDirectory');
if (!backupDirectoryData || !backupDirectoryData.id || !backupDirectoryData.path) {
console.error('Backup directory information is missing in application info.');
return;
}
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
if (!activeUsersIp || !activeUsersIp.length) {
console.error('No active users found.');
return;
}
const backupDirectoryId = backupDirectoryData.id;
const backupDirectoryPath = backupDirectoryData.path;
// Get the file structure from MemoryManager
const directoryData = await this.memoryManager.retrieveMetaInformation(backupDirectoryId);
if (!directoryData || !directoryData.structure) {
console.error('Backup directory structure is missing in memory.');
return;
}
// Send files to the list of IPs
await this.sendFilesToUsers(directoryData.structure, userName, activeUsersIp, backupDirectoryPath);
}
// Method to encrypt a file and return the base64 string
private encryptFile(filePath: string): string {
if (!this.fileEncryptor) {
return filePath;
}
if (!fs.existsSync(filePath)) {
console.error(`File not found: ${filePath}`);
return '';
}
// Use FileEncryptor to encrypt the file and return the base64 string
return this.fileEncryptor.encryptFileToBase64(filePath);
}
// Send files to the list of users and remove successfully sent files from the list
private async sendFilesToUsers(fileStructure: { [key: string]: string }, userName: string, usersIp: string[], backupDirectoryPath: string): Promise<void> {
let unsentFiles = Object.keys(fileStructure); // Keep track of unsent files
for (const fileName of unsentFiles) {
const filePath = fileStructure[fileName];
const encryptedFileContent = this.encryptFile(filePath);
if (!encryptedFileContent) {
console.error(`Failed to encrypt file: ${fileName}`);
continue; // Skip to the next file
}
// Calculate relative file path
const relativeFilePath = path.relative(backupDirectoryPath, filePath); // Get the relative file path
// Meta information to send
const metaInfo = {
userName, // Name of the user
relativeFilePath // Relative path to preserve the directory structure
};
for (const ip of usersIp) {
const tcpClient = new TcpClient(this.clientPort);
tcpClient.openSocket(ip);
// Wait for AES key and send the file
try {
const sendSuccess = await this.waitForAesKeyAndSendFile(tcpClient, operationCodes.BACKUP_FILE, metaInfo, Buffer.from(encryptedFileContent, 'base64'));
if (sendSuccess) {
console.log(`Successfully sent file: ${fileName} to ${ip}`);
unsentFiles = unsentFiles.filter(f => f !== fileName); // Remove the file from the unsent list
tcpClient.closeSocket();
break; // Move to the next file after successful send
}
} catch (error) {
console.error(`Failed to send file: ${fileName} to ${ip}. Error: ${error}`);
tcpClient.closeSocket();
}
}
}
// If there are any unsent files, notify the parent process
if (unsentFiles.length > 0) {
parentPort?.postMessage({ success: false, message: 'Backup could not be completed for all files', unsentFiles });
} else {
parentPort?.postMessage({ success: true, message: 'Backup completed successfully' });
}
}
// Wait for AES key to be set, send the file, and wait for the response
private async waitForAesKeyAndSendFile(tcpClient: TcpClient, operationCode: string, metaInfo: { [key: string]: any }, fileContent: Buffer, checkInterval: number = 500, timeout: number = 10000): Promise<boolean> {
const startTime = Date.now();
return new Promise((resolve, reject) => {
const intervalId = setInterval(async () => {
const elapsedTime = Date.now() - startTime;
// Check if AES key is set, if timeout occurs, reject
if (!tcpClient.isAesKeySet()) {
if (elapsedTime > timeout) {
clearInterval(intervalId);
console.error('Timeout waiting for AES key.');
reject(new Error('Timeout waiting for AES key.'));
}
return; // Continue waiting for AES key
}
// AES key is set, send the message
clearInterval(intervalId);
try {
const success = await tcpClient.sendMessage(operationCode, metaInfo, fileContent);
if (!success) {
reject(new Error('Failed to send the file content.'));
return;
}
// Wait for a response after sending the message
const responseReceived = await this.waitForMessageResponse(tcpClient, timeout);
if (!responseReceived) {
reject(new Error('Timeout waiting for the message response.'));
return;
}
// Everything went fine
resolve(true);
} catch (error) {
reject(error);
}
}, checkInterval); // Check for AES key every `checkInterval`
});
}
// Method to wait for the response from the TCP client
private waitForMessageResponse(tcpClient: TcpClient, timeout: number = 10000): Promise<any> {
return new Promise((resolve) => {
const startTime = Date.now();
const intervalId = setInterval(() => {
const response = tcpClient.getLastResult();
const elapsedTime = Date.now() - startTime;
if (response) {
clearInterval(intervalId);
resolve(true);
} else if (elapsedTime > timeout) {
clearInterval(intervalId);
resolve(false); // No response after timeout
}
}, 100);
});
}
}
+224
View File
@@ -0,0 +1,224 @@
import { JsonManager } from './json_manager'; // Assuming this manages JSON configurations
import { TcpClient } from '../network/tcp/tcp_client'; // Assuming this handles TCP client connections
import { operationCodes } from '../network/operation_codes'; // Assuming this holds operation codes
import { parentPort } from 'worker_threads';
import path from 'path';
import fs from 'fs';
import crypto from 'crypto'; // Import the crypto module for encryption and decryption
export class BackupRetrievalWorker {
private userConfig: JsonManager;
private applicationInfo: JsonManager;
private readonly clientPort: number;
private readonly destinationPath: string;
private encryptionKey: Buffer | null = null;
private iv: Buffer | null = null;
constructor(userConfigPath: string, applicationInfoPath: string, clientPort: number, destinationPath: string) {
this.userConfig = new JsonManager(userConfigPath);
this.applicationInfo = new JsonManager(applicationInfoPath);
this.clientPort = clientPort;
this.destinationPath = destinationPath;
}
async start(): Promise<void> {
// Retrieve the necessary data from userConfig and applicationInfo
const userInfo = await this.userConfig.readValue('user_info');
if (!userInfo || !userInfo.name) {
console.error('User information or name is missing.');
return;
}
const userName = userInfo.name;
// Load encryption key and IV from userConfig
const encryptionData = await this.userConfig.readValue('encryption_key');
if (!encryptionData || !encryptionData.key || !encryptionData.iv) {
console.error('Encryption key or IV is missing.');
return;
}
// Convert encryption key and IV from base64 to buffer
this.encryptionKey = Buffer.from(encryptionData.key, 'base64');
this.iv = Buffer.from(encryptionData.iv, 'base64');
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
if (!activeUsersIp || !activeUsersIp.length) {
console.error('No active users found.');
return;
}
// Process each IP and request backup data
for (const ip of activeUsersIp) {
try {
const success = await this.processBackupForIp(ip, userName);
if (success) {
console.log(`Backup retrieved successfully from ${ip}`);
} else {
console.error(`Failed to retrieve backup from ${ip}`);
}
} catch (error) {
console.error(`Error processing backup from ${ip}: ${error}`);
}
}
// Notify the parent that the worker is done
parentPort?.postMessage({ success: true, message: 'Backup retrieval completed.' });
}
private async processBackupForIp(ip: string, userName: string): Promise<boolean> {
const tcpClient = new TcpClient(this.clientPort);
tcpClient.openSocket(ip);
try {
// Wait for the AES key to be set before continuing
const aesSet = await this.waitForAesKey(tcpClient);
if (!aesSet) {
console.error(`Timeout waiting for AES key on IP ${ip}`);
return false;
}
// Step 1: Check if a backup exists for the user on this IP
const backupExists = await this.checkIfBackupExists(tcpClient, userName);
if (!backupExists) {
console.log(`No backup found for user ${userName} on IP ${ip}`);
return false;
}
// Step 2: Request the structure of the backup directory
const backupStructure = await this.requestBackupStructure(tcpClient, userName);
if (!backupStructure || Object.keys(backupStructure).length === 0) {
console.log(`No files found in backup structure for user ${userName} on IP ${ip}`);
return false;
}
// Step 3: Request and retrieve each file from the backup
for (const relativeFilePath of Object.keys(backupStructure)) {
const fileRequestSuccess = await this.requestBackupFile(tcpClient, userName, relativeFilePath);
if (!fileRequestSuccess) {
console.error(`Failed to retrieve file ${relativeFilePath} from backup for user ${userName} on IP ${ip}`);
return false; // Stop if any file fails to be retrieved
}
}
return true; // All files retrieved successfully
} catch (error) {
console.error(`Error during backup processing for IP ${ip}: ${error}`);
return false;
} finally {
tcpClient.closeSocket(); // Ensure socket is closed
}
}
// Wait for AES key to be set
private async waitForAesKey(tcpClient: TcpClient, timeout: number = 10000): Promise<boolean> {
const startTime = Date.now();
return new Promise((resolve, reject) => {
const intervalId = setInterval(() => {
const elapsedTime = Date.now() - startTime;
if (tcpClient.isAesKeySet()) {
clearInterval(intervalId);
resolve(true); // AES key is set, we can proceed
} else if (elapsedTime > timeout) {
clearInterval(intervalId);
resolve(false); // Timeout reached, AES key not set
}
}, 500); // Check every 500ms
});
}
// Wait for a message response with timeout
private async waitForMessageResponse(tcpClient: TcpClient, timeout: number = 10000): Promise<any> {
const startTime = Date.now();
return new Promise((resolve) => {
const intervalId = setInterval(() => {
const elapsedTime = Date.now() - startTime;
const response = tcpClient.getLastResult();
if (response) {
clearInterval(intervalId);
resolve(response); // Return the response
} else if (elapsedTime > timeout) {
clearInterval(intervalId);
resolve(null); // Timeout, no response
}
}, 500); // Check every 500ms
});
}
// Check if the backup exists for the user on the remote IP
private async checkIfBackupExists(tcpClient: TcpClient, userName: string): Promise<boolean> {
const metaInfo = { name: userName };
await tcpClient.sendMessage(operationCodes.IS_BACKUP_CREATED, metaInfo);
const response = await this.waitForMessageResponse(tcpClient);
return response?.metaInfo?.backupExists === true;
}
// Request the backup structure from the remote IP
private async requestBackupStructure(tcpClient: TcpClient, userName: string): Promise<any> {
const metaInfo = { name: userName };
await tcpClient.sendMessage(operationCodes.GET_BACKUP_STRUCTURE, metaInfo);
const response = await this.waitForMessageResponse(tcpClient);
return response?.metaInfo?.structure ? response.metaInfo.structure : null;
}
// Request a file from the backup and wait for it to be decrypted and stored
private async requestBackupFile(tcpClient: TcpClient, userName: string, relativeFilePath: string): Promise<boolean> {
const metaInfo = { name: userName, relativeFilePath };
await tcpClient.sendMessage(operationCodes.REQ_FILE_FROM_BACKUP, metaInfo);
const response = await this.waitForMessageResponse(tcpClient);
if (response?.operationCode === operationCodes.OK && response.metaInfo && response.fileContent) {
return this.saveFile(response.metaInfo.relativeFilePath, response.fileContent);
}
return false;
}
// Decrypt the file content using AES-256-CBC and save the decrypted file
private saveFile(relativeFilePath: string, fileContent: string): boolean {
if (!this.encryptionKey || !this.iv) {
console.error('Encryption key or IV is not set.');
return false;
}
// Decode the base64-encoded file content into a buffer
let encryptedBuffer: Buffer;
try {
encryptedBuffer = Buffer.from(fileContent, 'base64');
} catch (error) {
console.error('Error decoding base64 file content:', error);
return false;
}
// Decrypt the file content
let decryptedContent: Buffer;
try {
const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv);
decryptedContent = Buffer.concat([decipher.update(encryptedBuffer), decipher.final()]);
} catch (error) {
console.error('Error decrypting file:', error);
return false;
}
// Save the decrypted file content
const fullFilePath = path.join(this.destinationPath, relativeFilePath);
try {
const dirPath = path.dirname(fullFilePath);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true }); // Ensure directory exists
}
fs.writeFileSync(fullFilePath, decryptedContent);
console.log(`File saved successfully: ${fullFilePath}`);
return true;
} catch (error: any) {
console.error(`Error saving file ${relativeFilePath}: ${error.message}`);
return false;
}
}
}
+278
View File
@@ -0,0 +1,278 @@
import fs from 'fs';
import path from 'path';
import { TcpClient } from '../network/tcp/tcp_client'; // Assuming this class exists
import { operationCodes } from '../network/operation_codes';
import { JsonManager } from './json_manager'; // Manages JSON configurations
import { MemoryManager } from './memory_manager'; // Manages in-memory data structures
export class DepartmentSharer {
private userConfig: JsonManager;
private applicationInfo: JsonManager;
private memoryManager: MemoryManager; // To read the department files
private departmentDirectory: string | null;
private readonly clientPort: number;
constructor(
userConfigPath: string,
applicationInfoPath: string,
memoryManagerPath: string,
clientPort: number
) {
this.userConfig = new JsonManager(userConfigPath);
this.applicationInfo = new JsonManager(applicationInfoPath);
this.memoryManager = new MemoryManager(memoryManagerPath); // To retrieve files
this.clientPort = clientPort;
this.departmentDirectory = null;
}
// Start sharing files with the department every minute
async start(): Promise<void> {
setInterval(async () => {
await this.shareFilesWithDepartment(); // Retry every minute
}, 10000); // 10-second interval for testing
}
// Share files with users in the same department
private async shareFilesWithDepartment(): Promise<void> {
console.log('\n\nStarting Department Share Process\n\n');
// Get the current user's department information
const userInfo = await this.userConfig.readValue('user_info');
if (!userInfo || !userInfo.departmentId || !userInfo.name) {
console.error('User information or department ID is missing in the configuration.');
return;
}
const departmentId = userInfo.departmentId;
const userName = userInfo.name;
// Get the list of active users from applicationInfo
const activeUsersId = await this.applicationInfo.readValue('active_users_info');
if (!activeUsersId) {
console.error('No active users found.');
return;
}
const activeUsers = await this.memoryManager.retrieveMetaInformation(activeUsersId);
if (!activeUsers || activeUsers.length === 0) {
console.error('No active users found.');
return;
}
// Filter users who belong to the same department
const departmentUsers = activeUsers.filter((user: any) => user.user_info.departmentId === departmentId);
if (departmentUsers.length === 0) {
console.log('No users found in the same department.');
return;
}
// Get department directory info
const departmentData = await this.applicationInfo.readValue('departmentDirectory');
if (!departmentData || !departmentData.path || !departmentData.id) {
console.error('No department directory found.');
return;
}
this.departmentDirectory = departmentData.path;
// Read files from the MemoryManager related to this department
const departmentFiles = await this.memoryManager.retrieveMetaInformation(departmentData.id);
if (!departmentFiles || !departmentFiles.structure) {
console.error('No files found for this department in the memory manager.');
return;
}
// Send each file to every department user
await this.shareFilesWithUsers(departmentUsers, departmentFiles.structure, userName);
// After sending all files, clear the department directory
await this.clearDepartmentDirectory(departmentUsers);
}
// Send the files to the users in the department
private async shareFilesWithUsers(users: any[], files: { [key: string]: string }, userName: string): Promise<void> {
const unsentFiles = Object.keys(files); // Keep track of unsent files
for (const fileName of unsentFiles) {
const filePath = files[fileName];
// Ensure the file exists before attempting to send
if (!fs.existsSync(filePath)) {
console.error(`File not found: ${filePath}`);
continue;
}
// Read the file content
const fileContent = fs.readFileSync(filePath);
// Get the relative path of the file (used in the meta info)
if (!this.departmentDirectory) return;
const relativeFilePath = path.relative(this.departmentDirectory, filePath);
// Prepare the metaInfo (same structure as FileSharer)
const metaInfo = {
userName, // Sender's username
relativeFilePath // Use the relative path to preserve directory structure
};
// Send the file to each user in the same department
for (const user of users) {
const userIp = user.ip;
const tcpClient = new TcpClient(this.clientPort);
// Open a connection to the user's IP
tcpClient.openSocket(userIp);
try {
// Wait for the AES key and send the file
const success = await this.waitForAesKeyAndSendFile(tcpClient, operationCodes.DEPARTMENT_FILE, metaInfo, fileContent);
if (success) {
console.log(`File successfully sent: ${fileName} to user ${userIp}`);
unsentFiles.splice(unsentFiles.indexOf(fileName), 1); // Remove successfully sent file
tcpClient.closeSocket(); // Close the connection after sending
break; // Move to the next file after a successful send
}
} catch (error) {
console.error(`Failed to send file: ${fileName} to IP: ${userIp}. Error: ${error}`);
tcpClient.closeSocket(); // Ensure socket is closed on error
}
}
}
if (unsentFiles.length > 0) {
console.log('Some files could not be sent, retrying later.');
} else {
console.log('All files shared successfully.');
}
}
// Clear the department directory for all users after sharing
private async clearDepartmentDirectory(users: any[]): Promise<void> {
for (const user of users) {
const userIp = user.ip;
const tcpClient = new TcpClient(this.clientPort);
// Open a connection to the user's IP
tcpClient.openSocket(userIp);
try {
const success = await this.waitForAesKeyAndSendClearDepartment(tcpClient, operationCodes.CLEAR_DEPARTMENT);
if (success) {
console.log(`Department directory cleared successfully for user ${userIp}`);
} else {
console.error(`Failed to clear department directory for user ${userIp}`);
}
} catch (error) {
console.error(`Error clearing department directory for user ${userIp}:`, error);
} finally {
tcpClient.closeSocket(); // Ensure the socket is closed
}
}
}
// Wait for AES key to be set, send the file, and wait for the response
private async waitForAesKeyAndSendFile(tcpClient: TcpClient, operationCode: string, metaInfo: { [key: string]: any }, fileContent: Buffer, checkInterval: number = 500, timeout: number = 10000): Promise<boolean> {
const startTime = Date.now();
return new Promise((resolve, reject) => {
const intervalId = setInterval(async () => {
const elapsedTime = Date.now() - startTime;
// Check if AES key is set, if timeout occurs, reject
if (!tcpClient.isAesKeySet()) {
if (elapsedTime > timeout) {
clearInterval(intervalId);
console.error('Timeout waiting for AES key.');
reject(new Error('Timeout waiting for AES key.'));
}
return; // Continue waiting for AES key
}
// AES key is set, send the file
clearInterval(intervalId);
try {
const success = await tcpClient.sendMessage(operationCode, metaInfo, fileContent);
if (!success) {
reject(new Error('Failed to send the file content.'));
return;
}
// Wait for a response after sending the message
const responseReceived = await this.waitForMessageResponse(tcpClient, timeout);
if (!responseReceived) {
reject(new Error('Timeout waiting for the message response.'));
return;
}
resolve(true); // Everything went fine
} catch (error) {
reject(error);
}
}, checkInterval); // Check for AES key every `checkInterval`
});
}
// Wait for AES key to be set, send the clear department message, and wait for the response
private async waitForAesKeyAndSendClearDepartment(tcpClient: TcpClient, operationCode: string, checkInterval: number = 500, timeout: number = 10000): Promise<boolean> {
const startTime = Date.now();
return new Promise((resolve, reject) => {
const intervalId = setInterval(async () => {
const elapsedTime = Date.now() - startTime;
// Check if AES key is set, if timeout occurs, reject
if (!tcpClient.isAesKeySet()) {
if (elapsedTime > timeout) {
clearInterval(intervalId);
console.error('Timeout waiting for AES key.');
reject(new Error('Timeout waiting for AES key.'));
}
return; // Continue waiting for AES key
}
// AES key is set, send the clear department message
clearInterval(intervalId);
try {
const success = await tcpClient.sendMessage(operationCode, {});
if (!success) {
reject(new Error('Failed to send the clear department operation.'));
return;
}
// Wait for a response after sending the message
const responseReceived = await this.waitForMessageResponse(tcpClient, timeout);
if (!responseReceived) {
reject(new Error('Timeout waiting for the message response.'));
return;
}
resolve(true); // Everything went fine
} catch (error) {
reject(error);
}
}, checkInterval); // Check for AES key every `checkInterval`
});
}
// Wait for the response from the TCP client
private waitForMessageResponse(tcpClient: TcpClient, timeout: number = 10000): Promise<boolean> {
return new Promise((resolve) => {
const startTime = Date.now();
const intervalId = setInterval(() => {
const response = tcpClient.getLastResult();
const elapsedTime = Date.now() - startTime;
if (response?.operationCode === operationCodes.OK) {
clearInterval(intervalId);
resolve(true); // Message successfully received
} else if (elapsedTime > timeout) {
clearInterval(intervalId);
resolve(false); // No response after timeout
}
}, 100); // Check every 100ms
});
}
}
+129
View File
@@ -0,0 +1,129 @@
import { promises as fs, watch, FSWatcher } from 'fs';
import path from 'path';
import { MemoryManager } from './memory_manager';
import { JsonManager } from './json_manager';
export class DirectoryWatcher {
private directoryPath: string;
private directoryMemoryId: string;
private directoryScheme: any;
private applicationInfo: JsonManager;
private memoryManager: MemoryManager;
private readonly sourceKey: string;
private directoryWatcher: FSWatcher | null; // To store the watcher reference
private totalSize: number; // To store total directory size
constructor(memoryManagerPath: string, applicationInfoPath: string, sourceKey: string) {
this.sourceKey = sourceKey;
this.applicationInfo = new JsonManager(applicationInfoPath);
this.memoryManager = new MemoryManager(memoryManagerPath);
this.directoryMemoryId = '';
this.directoryPath = '';
this.directoryWatcher = null; // Initialize with no watcher
this.totalSize = 0; // Initialize size with zero
}
// Method to initialize and validate the backup directory
async initialize(): Promise<boolean> {
const directoryData = await this.applicationInfo.readValue(this.sourceKey);
if (!directoryData) {
return false;
}
this.directoryPath = directoryData.path;
this.directoryMemoryId = directoryData.id;
if (!this.directoryMemoryId || !this.directoryPath) {
console.error('Components of entry in \'DirectoryWatcher\' not found.');
await this.applicationInfo.removeValue(this.sourceKey);
return false;
}
if (!this.directoryScheme || Object.keys(this.directoryScheme).length === 0) {
// No structure in memory, scan and save it
const result = await this.buildDirectoryScheme(this.directoryPath);
this.directoryScheme = result.structure;
this.totalSize = result.size;
await this.memoryManager.updateMetaInformation(this.directoryMemoryId, {
structure: this.directoryScheme,
totalSize: this.totalSize,
});
}
// Start watching the directory (after stopping any existing watcher)
this.restartWatcher();
return true;
}
// Recursively build the directory structure and calculate the total size
private async buildDirectoryScheme(dirPath: string): Promise<{ structure: any, size: number }> {
const directoryScheme: any = {};
let totalSize = 0;
const items = await fs.readdir(dirPath, { withFileTypes: true });
for (const item of items) {
const fullPath = path.join(dirPath, item.name);
const stats = await fs.stat(fullPath); // Get stats for each item
if (item.isDirectory()) {
// If it's a directory, recursively build its structure and accumulate size
const { structure, size } = await this.buildDirectoryScheme(fullPath);
directoryScheme[item.name] = structure;
totalSize += size;
} else if (item.isFile()) {
// If it's a file, store its full path and accumulate size
directoryScheme[item.name] = fullPath;
totalSize += stats.size; // Add file size
}
}
return { structure: directoryScheme, size: totalSize };
}
// Restart the directory watcher, ensuring any previous watcher is closed
private restartWatcher(): void {
if (this.directoryWatcher) {
console.log('Stopping existing watcher...');
this.directoryWatcher.close(); // Stop the existing watcher
}
this.startDirectoryWatcher();
}
// Start watching the backup directory for changes
private startDirectoryWatcher(): void {
if (!this.directoryPath) {
throw new Error('Backup directory not set. Cannot start watcher.');
}
this.directoryWatcher = watch(this.directoryPath, { recursive: true }, async (eventType, filename) => {
if (filename) {
console.log(`File change detected: ${eventType} - ${filename}`);
// Rebuild the directory scheme and update memory
const result = await this.buildDirectoryScheme(this.directoryPath);
this.directoryScheme = result.structure;
this.totalSize = result.size;
await this.memoryManager.updateMetaInformation(this.directoryMemoryId, {
structure: this.directoryScheme,
totalSize: this.totalSize,
});
console.log('Directory structure and size updated in memory.');
}
});
console.log(`Watching for changes in: ${this.directoryPath}`);
}
// Close the directory watcher
public closeWatcher(): void {
if (this.directoryWatcher) {
console.log(`Stopping watcher for ${this.directoryPath}`);
this.directoryWatcher.close();
this.directoryWatcher = null; // Clear the reference after closing
}
}
}
+55
View File
@@ -0,0 +1,55 @@
import fs from 'fs';
import crypto from 'crypto';
export class FileEncryptor {
private readonly encryptionKey: Buffer;
private readonly iv: Buffer;
constructor(base64Key: string, base64Iv: string) {
// Decode the base64-encoded key and IV
this.encryptionKey = Buffer.from(base64Key, 'base64');
this.iv = Buffer.from(base64Iv, 'base64');
}
// Method to read a file, encrypt it, and return the encrypted content as a base64 string
public encryptFileToBase64(filePath: string): string {
try {
// Read the file contents
const fileBuffer = fs.readFileSync(filePath);
// Create the cipher using AES-256-CBC (or another algorithm you prefer)
const cipher = crypto.createCipheriv('aes-256-cbc', this.encryptionKey, this.iv);
// Encrypt the file data
let encryptedData = cipher.update(fileBuffer);
encryptedData = Buffer.concat([encryptedData, cipher.final()]);
// Return the encrypted data as a base64 string
return encryptedData.toString('base64');
} catch (err) {
console.error(`Error encrypting file at path ${filePath}:`, err);
throw err;
}
}
// Method to decrypt base64-encoded encrypted content and return the decrypted buffer
public decryptBase64(encryptedBase64: string): Buffer {
try {
// Decode the base64-encoded encrypted data
const encryptedData = Buffer.from(encryptedBase64, 'base64');
// Create the decipher using AES-256-CBC
const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv);
// Decrypt the data
let decryptedData = decipher.update(encryptedData);
decryptedData = Buffer.concat([decryptedData, decipher.final()]);
// Return the decrypted buffer
return decryptedData;
} catch (err) {
console.error('Error decrypting data:', err);
throw err;
}
}
}
+152
View File
@@ -0,0 +1,152 @@
import { QueueManager } from './queue_manager'; // Assume the QueueManager is in this path
import { TcpClient } from '../network/tcp/tcp_client'; // Import your TcpClient class
import { operationCodes } from '../network/operation_codes';
import fs from "fs";
import path from "path";
import {compareFnFileItemTask, FileItemTask} from "../interfaces/file_item_task";
interface FileSendTask {
ip: string;
path: string;
userName: string;
}
export class FileSharer {
private queueManager: QueueManager<FileSendTask>;
private readonly clientPort: number;
constructor(queueFilePath: string, clientPort: number) {
this.queueManager = new QueueManager<FileItemTask>(queueFilePath, compareFnFileItemTask);
this.clientPort = clientPort;
}
// Start processing the file queue
async start(): Promise<void> {
setInterval(async () => {
await this.processQueue(); // Process the queue at regular intervals
}, 10000); // 10 seconds interval
}
// Method to process the queue
private async processQueue(): Promise<void> {
while (!this.queueManager.isEmpty()) {
const task = this.queueManager.peek();
if (task) {
const success = await this.sendFile(task);
if (!success) {
console.error(`Failed to send file: ${task.path} to IP: ${task.ip}. Re-adding to queue.`);
this.queueManager.dequeue();
this.queueManager.enqueue(task); // Re-add to queue if failed
}
else{
console.log(`File sent successfully: ${task.path} to IP: ${task.ip}.`);
this.queueManager.dequeue();
}
}
}
}
// Method to send the file to a specific IP using the TcpClient
private async sendFile(task: FileSendTask): Promise<boolean> {
const { ip, path: filePath, userName } = task;
// Ensure the file exists before attempting to send
if (!fs.existsSync(filePath)) {
console.error(`File not found: ${filePath}`);
return false;
}
// Read the file contents
const fileContent = fs.readFileSync(filePath);
// Extract the file name from the file path using path.basename (handles both Windows and Unix)
const fileName = path.basename(filePath);
const metaInfo = {
userName, // Sender's username
relativeFilePath: fileName, // Use the file name instead of the full path
};
const tcpClient = new TcpClient(this.clientPort);
tcpClient.openSocket(ip);
// Wait for the AES key and then send the file
try {
const sendSuccess = await this.waitForAesKeyAndSendFile(tcpClient, operationCodes.SHARE_FILE, metaInfo, fileContent, task);
tcpClient.closeSocket();
return sendSuccess;
} catch (error) {
console.error(`Error sending file: ${filePath} to IP: ${ip}. Error: ${error}`);
tcpClient.closeSocket();
return false;
}
}
// Wait for AES key to be set and send the file, handling the response
private async waitForAesKeyAndSendFile(tcpClient: TcpClient, operationCode: string, metaInfo: { [key: string]: any }, fileContent: Buffer, task: FileSendTask, checkInterval: number = 500, timeout: number = 10000): Promise<boolean> {
const startTime = Date.now();
return new Promise((resolve, reject) => {
const intervalId = setInterval(async () => {
const elapsedTime = Date.now() - startTime;
// Check if AES key is set, if timeout occurs, reject
if (!tcpClient.isAesKeySet()) {
if (elapsedTime > timeout) {
clearInterval(intervalId);
console.error('Timeout waiting for AES key.');
reject(new Error('Timeout waiting for AES key.'));
}
return; // Continue waiting for AES key
}
// AES key is set, send the message
clearInterval(intervalId);
try {
const success = await tcpClient.sendMessage(operationCode, metaInfo, fileContent);
if (!success) {
reject(new Error('Failed to send the file content.'));
return;
}
// Wait for a response after sending the message
const responseReceived = await this.waitForMessageResponse(tcpClient, timeout);
if (!responseReceived) {
reject(new Error('Timeout waiting for the message response.'));
return;
}
resolve(true); // Everything went fine
} catch (error) {
reject(error);
}
}, checkInterval); // Check for AES key every `checkInterval`
});
}
// Method to wait for the response from the TCP client
private waitForMessageResponse(tcpClient: TcpClient, timeout: number = 10000): Promise<boolean> {
return new Promise((resolve) => {
const startTime = Date.now();
const intervalId = setInterval(() => {
const response = tcpClient.getLastResult();
const elapsedTime = Date.now() - startTime;
if (response?.operationCode === operationCodes.OK) {
clearInterval(intervalId);
resolve(true); // Message successfully received
} else if (elapsedTime > timeout) {
clearInterval(intervalId);
resolve(false); // No response after timeout
}
}, 100); // Check every 100ms
});
}
}
+119
View File
@@ -0,0 +1,119 @@
import fs from 'fs';
import path from 'path';
export class JsonManager {
private readonly filePath: string;
private readonly lockFilePath: string;
constructor(filePath: string) {
const dir = path.dirname(filePath);
// Check if the directory exists, throw error if it doesn't
if (!fs.existsSync(dir)) {
throw new Error(`The directory does not exist: ${dir}`);
}
this.filePath = filePath;
this.lockFilePath = `${filePath}.lock`; // Define the lock file path
// If the file doesn't exist, create it
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, JSON.stringify({}, null, 2), 'utf8');
}
}
// Method to acquire a lock (create .lock file)
private async acquireLock(): Promise<void> {
while (fs.existsSync(this.lockFilePath)) {
// Wait until the lock file is released
await new Promise(resolve => setTimeout(resolve, 100)); // 100ms delay before retrying
}
// Create the lock file
fs.writeFileSync(this.lockFilePath, '');
}
// Method to release the lock (delete .lock file)
private releaseLock(): void {
if (fs.existsSync(this.lockFilePath)) {
fs.unlinkSync(this.lockFilePath);
}
}
// Read a value by key from the JSON file with a lock
public async readValue(key: string): Promise<any | null> {
await this.acquireLock(); // Acquire the lock
try {
if (!fs.existsSync(this.filePath)) return null;
const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
return data[key] !== undefined ? data[key] : null;
} catch (err: any) {
console.error(`Error reading from JSON file: ${err.message}`);
return null;
} finally {
this.releaseLock(); // Always release the lock after the operation
}
}
// Write a key-value pair to the JSON file with a lock
public async writeValue(key: string, value: any): Promise<boolean> {
await this.acquireLock(); // Acquire the lock
try {
let data: { [key: string]: any } = {};
if (fs.existsSync(this.filePath)) {
data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
}
// Update the key with the new value
data[key] = value;
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8');
return true;
} catch (err: any) {
console.error(`Error writing to JSON file: ${err.message}`);
return false;
} finally {
this.releaseLock(); // Always release the lock after the operation
}
}
// Remove a key-value pair from the JSON file with a lock
public async removeValue(key: string): Promise<boolean> {
await this.acquireLock(); // Acquire the lock
try {
if (!fs.existsSync(this.filePath)) return false;
const data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
if (data[key] !== undefined) {
delete data[key];
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf8');
return true;
}
return false;
} catch (err: any) {
console.error(`Error removing key from JSON file: ${err.message}`);
return false;
} finally {
this.releaseLock(); // Always release the lock after the operation
}
}
// Reset the JSON file by clearing all data with a lock
public async resetFile(): Promise<boolean> {
await this.acquireLock(); // Acquire the lock
try {
fs.writeFileSync(this.filePath, JSON.stringify({}, null, 2), 'utf8');
return true;
} catch (err: any) {
console.error(`Error resetting JSON file: ${err.message}`);
return false;
} finally {
this.releaseLock(); // Always release the lock after the operation
}
}
}
+47
View File
@@ -0,0 +1,47 @@
import { v4 as uuidv4 } from 'uuid';
import { JsonManager } from './json_manager';
export class MemoryManager extends JsonManager {
constructor(filePath: string) {
super(filePath); // Call the parent constructor to ensure file initialization
}
// Generate a new unique GUID and ensure it doesn't already exist in the file
private generateUniqueGuid(): Promise<string> {
const generate = async (): Promise<string> => {
const guid = uuidv4();
const value = await this.readValue(guid);
if (value === null) {
return guid;
}
return generate();
};
return generate();
}
// Store meta information with a unique GUID as the key
public async storeMetaInformation(metaInfo: any): Promise<string> {
const guid = await this.generateUniqueGuid();
const success = await this.writeValue(guid, metaInfo);
if (success) {
return guid; // Return the unique GUID for future reference
} else {
throw new Error('Failed to store meta information.');
}
}
// Retrieve meta information using the GUID
public retrieveMetaInformation(guid: string): Promise<any | null> {
return this.readValue(guid);
}
// Update meta information by merging new data into existing data
public async updateMetaInformation(guid: string, newMetaInfo: any): Promise<boolean> {
return await this.writeValue(guid, newMetaInfo);
}
// Remove meta information using the GUID
public removeMetaInformation(guid: string): Promise<boolean> {
return this.removeValue(guid);
}
}
+137
View File
@@ -0,0 +1,137 @@
import fs from 'fs';
import path from 'path';
export class QueueManager<T> {
private readonly filePath: string;
private readonly lockFilePath: string;
private queue: T[];
private readonly compareFn: (a: T, b: T) => boolean; // Comparison function
constructor(filePath: string, compareFn: (a: T, b: T) => boolean) {
this.filePath = filePath;
this.lockFilePath = `${filePath}.lock`; // Define the lock file path
this.queue = [];
this.compareFn = compareFn;
const dir = path.dirname(filePath);
// Check if the directory exists, throw error if it doesn't
if (!fs.existsSync(dir)) {
throw new Error(`The directory does not exist: ${dir}`);
}
// If the file doesn't exist, create it
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, JSON.stringify([], null, 2), 'utf8');
}
}
// Method to acquire a lock (create .lock file)
private acquireLock(): void {
while (fs.existsSync(this.lockFilePath)) {
// Wait until the lock file is released
this.sleepSync(100); // 100ms delay before retrying
}
// Create the lock file
fs.writeFileSync(this.lockFilePath, '');
}
// Sleep function to simulate delay for locking mechanism
private sleepSync(ms: number): void {
const start = Date.now();
while (Date.now() - start < ms) {
// busy wait
}
}
// Method to release the lock (delete .lock file)
private releaseLock(): void {
if (fs.existsSync(this.lockFilePath)) {
fs.unlinkSync(this.lockFilePath);
}
}
// Load the queue from the JSON file
loadQueue(): void {
this.acquireLock(); // Acquire the lock
try {
const fileData = fs.readFileSync(this.filePath, 'utf8');
this.queue = JSON.parse(fileData) || [];
} catch (err) {
// If the file doesn't exist or is invalid, start with an empty queue
this.queue = [];
} finally {
this.releaseLock(); // Release the lock
}
}
// Save the queue back to the JSON file
saveQueue(): void {
this.acquireLock(); // Acquire the lock
try {
fs.writeFileSync(this.filePath, JSON.stringify(this.queue, null, 2), 'utf8');
} finally {
this.releaseLock(); // Release the lock
}
}
// Enqueue: Add an item to the end of the queue if it doesn't already exist
enqueue(item: T): void {
this.loadQueue(); // Ensure we load the latest queue
// Check if the item already exists in the queue
const exists = this.queue.some(existingItem => this.compareFn(existingItem, item));
console.log(this.queue);
if (!exists) {
this.queue.push(item);
this.saveQueue(); // Save the updated queue
} else {
console.log('Item already exists in the queue. Skipping enqueue.');
}
}
// Dequeue: Remove an item from the front of the queue
dequeue(): T | null {
this.loadQueue(); // Ensure we load the latest queue
if (this.queue.length === 0) {
return null; // Queue is empty
}
const item = this.queue.shift() as T; // Remove the first item
this.saveQueue(); // Save the updated queue
return item;
}
// Peek: Get the item at the front of the queue without removing it
peek(): T | null {
this.loadQueue(); // Ensure we load the latest queue
return this.queue.length > 0 ? this.queue[0] : null;
}
// Check if the queue is empty
isEmpty(): boolean {
this.loadQueue(); // Ensure we load the latest queue
return this.queue.length === 0;
}
// Get the length of the queue
length(): number {
this.loadQueue(); // Ensure we load the latest queue
return this.queue.length;
}
// Clear the entire queue
clearQueue(): void {
this.acquireLock(); // Acquire the lock
try {
this.queue = []; // Clear the queue
fs.writeFileSync(this.filePath, JSON.stringify(this.queue, null, 2), 'utf8');
} finally {
this.releaseLock(); // Release the lock
}
}
}
+76
View File
@@ -0,0 +1,76 @@
import { JsonManager } from './json_manager'; // Assuming you have this class for managing user/memory
import { WindowManager } from './window_manager'; // For managing app navigation
import { UdpClient } from '../network/udp/udp_client';
export class TaskScheduler {
private applicationInfo: JsonManager;
private windowManager: WindowManager;
private appStarted: boolean = false;
private intervalIds: NodeJS.Timeout[] = []; // Array to store interval IDs
constructor(applicationInfo: JsonManager, windowManager: WindowManager) {
this.applicationInfo = applicationInfo;
this.windowManager = windowManager;
}
// Function to schedule the UC check task with dynamic UDP client creation
public startUCCheck(udpPort: number, okPage: string, errorPage: string, interval: number = 30000) {
const intervalId = setInterval(async () => {
try {
const udpClient = new UdpClient(udpPort); // Create UdpClient with the provided port
const aliveClients = await udpClient.getAliveClients();
const storedIp = await this.applicationInfo.readValue('serverIp');
const foundClient = aliveClients.length > 0;
if (foundClient) {
const ipAddress = aliveClients[0]; // Just using the first alive client
if (!storedIp || storedIp !== ipAddress) {
await this.applicationInfo.writeValue('serverIp', ipAddress);
if (!this.appStarted) {
await this.windowManager.changeContent(okPage);
}
this.appStarted = true;
} else if (!this.appStarted) {
await this.windowManager.changeContent(okPage);
this.appStarted = true;
}
} else {
await this.windowManager.changeContent(errorPage);
}
} catch (err) {
console.error('Error checking UC:', err);
await this.windowManager.changeContent(errorPage);
}
}, interval);
this.intervalIds.push(intervalId);
}
// Function to schedule the IP lookup task, storing the active addresses in memory
public async startUserIPLookup(udpPort: number, interval: number = 30000) {
const serverIp = await this.applicationInfo.readValue('serverIp'); // Ensure it's awaited if it's an async function
const intervalId = setInterval(async () => {
try {
const udpClient = new UdpClient(udpPort); // Create a UDP client with the provided port
const activeIPs = await udpClient.getAliveClients(); // Get the list of active IPs
// Filter out the serverIp from the list of active clients
const filteredIPs = activeIPs.filter(ip => ip !== serverIp);
// Save the filtered IPs to 'users_ip'
await this.applicationInfo.writeValue('users_ip', filteredIPs);
} catch (err) {
console.error('Error during user IP lookup:', err);
}
}, interval);
this.intervalIds.push(intervalId); // Store the interval ID
}
// Function to stop all tasks (UC check, user worker, etc.)
public stopAllTasks() {
this.intervalIds.forEach(clearInterval); // Clear all intervals
this.intervalIds = []; // Reset the array
}
}
+81
View File
@@ -0,0 +1,81 @@
import { TcpClient } from "../network/tcp/tcp_client";
import { ParsedMessage } from "../network/message_handler";
export class TcpCommunicator {
private readonly ip: string;
private readonly port: number;
private tcpClient: TcpClient | null = null;
private lastResult: ParsedMessage | null = null;
constructor(ip: string, port: number) {
this.ip = ip;
this.port = port;
}
async connect(): Promise<boolean> {
this.tcpClient = new TcpClient(this.port);
this.tcpClient.openSocket(this.ip);
return this.tcpClient.isSocketConnected();
}
async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise<boolean> {
if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return false;
// Wait until the AES key is set before sending the message
return new Promise((resolve) => {
const idWaitForAes = setInterval(async () => {
if (this.tcpClient?.isAesKeySet()) {
clearInterval(idWaitForAes);
// Send the message once AES key is set
const status = await this.tcpClient!.sendMessage(operationCode, metaInfo, fileContent);
if (status) {
await this.waitForResponse();
}
resolve(status);
}
}, 100);
});
}
getLastResult(): ParsedMessage | null {
const message = this.lastResult;
this.lastResult = null;
return message;
}
hasResponseArrived(): boolean {
if(!this.tcpClient) return false;
return this.lastResult !== null;
}
private waitForResponse(): Promise<void> {
return new Promise((resolve, reject) => {
if (!this.tcpClient) {
reject();
}
// Start interval for waiting for the response
const responseInterval = setInterval(() => {
if (!this.tcpClient?.isSocketConnected()) {
clearInterval(responseInterval);
resolve();
}
if (this.tcpClient?.isMessageReceived()) {
this.lastResult = this.tcpClient.getLastResult();
clearInterval(responseInterval); // Stop checking once we have a response
resolve();
}
}, 100); // Check every 100 milliseconds
});
}
async disconnect(): Promise<boolean> {
if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return true;
this.tcpClient.closeSocket();
return true;
}
}
+111
View File
@@ -0,0 +1,111 @@
import { JsonManager } from "./json_manager";
import { MemoryManager } from "./memory_manager";
import { operationCodes } from "../network/operation_codes";
import { TcpCommunicator } from "./tcp_communicator";
export class UsersInfoFetcher {
private applicationInfo: JsonManager;
private memoryManager: MemoryManager;
private tcpCommunicator: TcpCommunicator | null = null;
private readonly clientPort: number;
private memoryId: string;
private readonly activeUsersKey: string;
constructor(applicationInfoPath: string, memoryManagerPath: string, clientPort: number) {
this.applicationInfo = new JsonManager(applicationInfoPath);
this.memoryManager = new MemoryManager(memoryManagerPath);
this.memoryId = '';
this.clientPort = clientPort;
this.tcpCommunicator = null;
this.activeUsersKey = 'active_users_info';
}
// Method to start checking user info periodically (every minute)
async start(): Promise<void> {
setInterval(async () => {
await this.initialize(); // Re-run every minute
}, 60000); // 1 minute interval
}
// Initialize and fetch user IPs and process users info
private async initialize() {
const usersIps = await this.applicationInfo.readValue('users_ip');
if (!usersIps) {
console.error('No IP addresses found in users_ip');
return;
}
// Ensure active_users_info exists in the memory
this.memoryId = await this.applicationInfo.readValue(this.activeUsersKey);
if (!this.memoryId) {
this.memoryId = await this.memoryManager.storeMetaInformation([]);
await this.applicationInfo.writeValue(this.activeUsersKey, this.memoryId);
}
// Check user information
await this.checkUsersInfo(usersIps);
}
// Check user info from the list of IPs
private async checkUsersInfo(usersIps: string[]) {
let usersInfo: Array<{ ip: string, user_info: any }> = []; // Array to store IP and user_info objects
for (const ip of usersIps) {
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort);
if (!await this.tcpCommunicator.connect()) {
console.error(`Failed to open connection for IP: ${ip}`);
continue;
}
// Wait for the response for 10 seconds
const response = await this.waitForResponse();
console.log(response);
// If a response is received and is successful, append it to usersInfo
if (response && response.metaInfo) {
console.log(`Response received from ${ip}:`, response);
usersInfo.push({
ip: ip,
user_info: response.metaInfo // Push the IP and user_info object into the array
});
}
await this.tcpCommunicator.disconnect();
}
await this.updateActiveUsers(usersInfo); // Update active users information in the memory
}
// Send the message once and wait for the response for a specific timeout
private async waitForResponse(timeout: number = 10000): Promise<any> {
// Send the message once
const status = await this.tcpCommunicator?.sendMessage(operationCodes.GET_USER_INFORMATION);
if (!status) {
return null;
}
// Wait for the response until the timeout
return new Promise((resolve) => {
const startTime = Date.now();
const checkResponseInterval = setInterval(async () => {
// Check if the message has been received
if (this.tcpCommunicator?.hasResponseArrived()) {
clearInterval(checkResponseInterval);
resolve(this.tcpCommunicator?.getLastResult()); // Return the response once it arrives
}
// If the timeout is reached, stop checking and resolve with null
if (Date.now() - startTime > timeout) {
clearInterval(checkResponseInterval);
resolve(null);
}
}, 100); // Check every 100ms if the response has arrived
});
}
// Update active users information in the memory
private async updateActiveUsers(userInfo: any[]) {
await this.memoryManager.updateMetaInformation(this.memoryId, userInfo); // Update active users in memory
}
}
+92
View File
@@ -0,0 +1,92 @@
import { BrowserWindow, dialog, shell } from 'electron';
import fs from 'fs';
import path from 'path';
export class WindowManager {
private readonly mainWindow: BrowserWindow;
private readonly pathToPagesDir: string;
constructor(mainWindow: BrowserWindow, pathToPagesDir: string) {
this.pathToPagesDir = pathToPagesDir;
this.mainWindow = mainWindow;
}
// Show an alert dialog
async showAlert(message: string): Promise<void> {
if (this.mainWindow) {
await dialog.showMessageBox(this.mainWindow, {
type: 'info',
title: 'Alert',
message: message,
buttons: ['OK'],
});
} else {
console.error('Main window is not available.');
}
}
// Change the content of the current window to load a new HTML file
async changeContent(destination: string): Promise<void> {
if (this.mainWindow) {
try {
const destinationPath = path.join(this.pathToPagesDir, `${destination}.html`);
console.log(`Navigating to: ${destinationPath}`);
// Load the destination HTML file into the main window
await this.mainWindow.loadFile(destinationPath);
console.log(`Navigated to ${destination}`);
} catch (error) {
console.error('Error changing content:', error);
throw error; // Pass the error back to the render process
}
} else {
console.error('Main window is not available.');
}
}
// New method to select a directory
async selectDirectory(): Promise<string | undefined> {
const result = await dialog.showOpenDialog(this.mainWindow, {
properties: ['openDirectory'], // Only allow selecting directories
});
// If the user cancels, result.filePaths will be an empty array
if (result.filePaths && result.filePaths.length > 0) {
return result.filePaths[0]; // Return the selected directory path
} else {
console.log('No directory selected.');
return undefined; // Return undefined if no directory was selected
}
}
async showFileInExplorer(filePath: string): Promise<void> {
if (filePath && fs.existsSync(filePath)) {
try {
// Use Electron's shell module to show the file in the explorer
shell.showItemInFolder(filePath);
console.log(`Opened file explorer for: ${filePath}`);
} catch (error: any) {
console.error(`Error showing file in explorer: ${error.message}`);
}
} else {
console.error('File path is undefined or does not exist.');
}
}
// New method to open the file explorer and choose a file
async selectFile(): Promise<string | undefined> {
const result = await dialog.showOpenDialog(this.mainWindow, {
properties: ['openFile'], // Allow selecting a file
filters: [
{ name: 'All Files', extensions: ['*'] } // Optionally filter for specific file types
]
});
if (result.filePaths && result.filePaths.length > 0) {
return result.filePaths[0]; // Return the selected file path
} else {
console.log('No file selected.');
return undefined; // Return undefined if no file was selected
}
}
}
+155
View File
@@ -0,0 +1,155 @@
import { Worker } from 'worker_threads';
import path from 'path';
import {WindowManager} from "./window_manager";
export class WorkerManager {
private readonly pathToWorkerDir: string;
private windowManager: WindowManager
private workers: Worker[]; // Array to store running workers
constructor(pathToWorkerDir: string, windowManager: WindowManager) {
this.pathToWorkerDir = pathToWorkerDir;
this.windowManager = windowManager;
this.workers = []; // Initialize the array to store workers
}
// Start the Connection Pool Worker
async startWatchersWorker(memoryManagerPath: string, applicationInfoPath: string): Promise<void> {
return new Promise((resolve, reject) => {
const worker = new Worker(path.join(this.pathToWorkerDir, 'watcher_worker.js'), {
workerData: { memoryManagerPath, applicationInfoPath }, // Pass the port to the worker
});
this.workers.push(worker); // Store the worker reference
worker.on('message', (data) => {
console.log('Connection Pool Worker message:', data);
});
worker.on('error', (err) => {
console.error('Connection Pool Worker error:', err);
worker.terminate();
this.removeWorker(worker);
reject(err); // Reject the promise if there's an error
});
worker.on('exit', (code) => {
console.log(`Connection Pool Worker exited with code ${code}`);
this.removeWorker(worker); // Remove worker reference when it exits
resolve(); // Resolve when the worker exits cleanly
});
});
}
// Start the Servers Worker (UDP and TCP servers)
async startServersWorker(host: string, udpPort: number, tcpPort: number): Promise<void> {
return new Promise((resolve, reject) => {
const worker = new Worker(path.join(this.pathToWorkerDir, 'servers_worker.js'), {
workerData: { HOST: host, USER_UDP_PORT: udpPort, USER_TCP_PORT: tcpPort }, // Pass host and ports to the worker
});
this.workers.push(worker); // Store the worker reference
worker.on('message', (data) => {
console.log('Servers Worker message:', data);
});
worker.on('error', (err) => {
console.error('Servers Worker error:', err);
worker.terminate();
this.removeWorker(worker);
reject(err); // Reject the promise if there's an error
});
worker.on('exit', (code) => {
console.log(`Servers Worker exited with code ${code}`);
this.removeWorker(worker); // Remove worker reference when it exits
resolve(); // Resolve when the worker exits cleanly
});
});
}
// Start the Users Info Worker
async startResourceCoordinatorWorker(usersConfigPath: string, applicationInfoPath: string, memoryManagerPath: string, queueManagerPath: string, tcpPort: number): Promise<void> {
return new Promise((resolve, reject) => {
const worker = new Worker(path.join(this.pathToWorkerDir, 'resource_coordinator_worker.js'), {
workerData: {
usersConfigPath,
applicationInfoPath,
memoryManagerPath,
queueManagerPath,
tcpPort
}, // Pass necessary parameters to the worker
});
this.workers.push(worker); // Store the worker reference
worker.on('message', (data) => {
console.log('Users Info Worker message:', data);
});
worker.on('error', (err) => {
console.error('Users Info Worker error:', err);
worker.terminate();
this.removeWorker(worker);
reject(err); // Reject the promise if there's an error
});
worker.on('exit', (code) => {
console.log(`Users Info Worker exited with code ${code}`);
this.removeWorker(worker); // Remove worker reference when it exits
resolve(); // Resolve when the worker exits cleanly
});
});
}
// Start the Backup Retrieval Worker
async startBackupRetrievalWorker(
userConfigPath: string,
applicationInfoPath: string,
clientPort: number,
destinationPath: string
): Promise<void> {
return new Promise((resolve, reject) => {
const worker = new Worker(path.join(this.pathToWorkerDir, 'backup_retrieval_worker.js'), {
workerData: { userConfigPath, applicationInfoPath, clientPort, destinationPath }, // Pass parameters to the worker
});
this.workers.push(worker); // Store the worker reference
worker.on('message', (data) => {
console.log('Backup Retrieval Worker message:', data);
this.windowManager.changeContent('main_menu');
this.windowManager.showAlert(data.message);
});
worker.on('error', (err) => {
console.error('Backup Retrieval Worker error:', err);
worker.terminate();
this.removeWorker(worker);
reject(err); // Reject the promise if there's an error
});
worker.on('exit', (code) => {
console.log(`Backup Retrieval Worker exited with code ${code}`);
this.removeWorker(worker); // Remove worker reference when it exits
resolve(); // Resolve when the worker exits cleanly
});
});
}
// Close all running workers
closeAllWorkers(): void {
console.log('Terminating all running workers...');
this.workers.forEach(worker => worker.terminate()); // Terminate each worker
this.workers = []; // Clear the array after terminating all workers
}
// Helper method to remove a worker from the workers array when it exits
private removeWorker(worker: Worker): void {
const index = this.workers.indexOf(worker);
if (index > -1) {
this.workers.splice(index, 1); // Remove the worker from the array
}
}
}
+8
View File
@@ -0,0 +1,8 @@
export interface FileItemTask {
ip: string;
path: string;
userName: string;
}
export const compareFnFileItemTask = (task1: FileItemTask, task2: FileItemTask) =>
task1.ip === task2.ip && task1.path === task2.path;
+15
View File
@@ -0,0 +1,15 @@
interface PoolRequest {
type: PoolOperation; // Renamed to PoolOperation
clientId: string; // Add clientId to the request
data: PoolDataBundle;
}
interface PoolDataBundle {
port?: number;
ip?: string;
operationCode?: string; // Keep operationCode here
metaInfo?: { [key: string]: any };
fileContent?: Buffer;
}
type PoolOperation = 'open' | 'send' | 'close'; // Define the allowed PoolOperations
+5
View File
@@ -0,0 +1,5 @@
interface RegisteredClient {
id: string;
ip: string;
port: number;
}
-115
View File
@@ -1,115 +0,0 @@
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
async function readKeyFromFile(filePath) {
try {
await fs.promises.access(filePath, fs.constants.F_OK);
return fs.promises.readFile(filePath); // Use asynchronous readFile
} catch (error) {
console.error('Error reading key file:', error);
return null;
}
}
// Paths to the key files remain the same
const IV_FILE_PATH = path.join(__dirname, '..', '..', 'iv.key');
const SECRET_KEY_FILE_PATH = path.join(__dirname, '..', '..', 'secret.key');
async function encryptFileInPlace(filePath) {
// Await the resolution of these promises
const IV = await readKeyFromFile(IV_FILE_PATH);
const SECRET_KEY = await readKeyFromFile(SECRET_KEY_FILE_PATH);
if (!IV || !SECRET_KEY) {
throw new Error('Failed to load IV or Secret Key');
}
const tempEncryptedFilePath = filePath + '.enc'; // Temporary encrypted file
return new Promise((resolve, reject) => {
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(SECRET_KEY), IV);
const input = fs.createReadStream(filePath);
const output = fs.createWriteStream(tempEncryptedFilePath);
input.pipe(cipher).pipe(output);
output.on('finish', () => {
fs.rename(tempEncryptedFilePath, filePath, (err) => {
if (err) reject(err);
else resolve('File encrypted successfully and replaced original.');
});
});
output.on('error', reject);
});
}
async function decryptFileInPlace(filePath) {
// Await the resolution of these promises
const IV = await readKeyFromFile(IV_FILE_PATH);
const SECRET_KEY = await readKeyFromFile(SECRET_KEY_FILE_PATH);
if (!IV || !SECRET_KEY) {
throw new Error('Failed to load IV or Secret Key');
}
const tempDecryptedFilePath = filePath + '.dec'; // Temporary decrypted file
return new Promise((resolve, reject) => {
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(SECRET_KEY), IV);
const input = fs.createReadStream(filePath);
const output = fs.createWriteStream(tempDecryptedFilePath);
input.pipe(decipher).pipe(output);
output.on('finish', () => {
fs.rename(tempDecryptedFilePath, filePath, (err) => {
if (err) reject(err);
else resolve('File decrypted successfully and replaced original.');
});
});
output.on('error', reject);
});
}
function cryptForKey(content, key, decrypt = false) {
const algorithm = 'aes-256-ctr';
const secretKey = crypto.createHash('sha256').update(String(key)).digest('base64').substr(0, 32);
let cipher;
if (decrypt) {
cipher = crypto.createDecipheriv(algorithm, secretKey, Buffer.alloc(16, 0)); // Using a zeroed IV for CTR
} else {
cipher = crypto.createCipheriv(algorithm, secretKey, Buffer.alloc(16, 0));
}
return Buffer.concat([cipher.update(content), cipher.final()]);
}
// Encrypts a file in place with a given key
async function encryptFileWithKey(filePath, key) {
try {
const fileContent = await fs.promises.readFile(filePath);
const encryptedContent = cryptForKey(fileContent, key, false);
await fs.promises.writeFile(filePath, encryptedContent);
console.log(`File encrypted successfully: ${filePath}`);
} catch (error) {
console.error(`Error encrypting file: ${error.message}`);
}
}
async function decryptFileWithKey(filePath, key) {
try {
const fileContent = await fs.promises.readFile(filePath);
const decryptedContent = cryptForKey(fileContent, key, true);
await fs.promises.writeFile(filePath, decryptedContent);
console.log(`File decrypted successfully: ${filePath}`);
} catch (error) {
console.error(`Error decrypting file: ${error.message}`);
}
}
module.exports = {
encryptFileInPlace,
decryptFileInPlace,
encryptFileWithKey,
decryptFileWithKey,
}
-422
View File
@@ -1,422 +0,0 @@
const {app, BrowserWindow, screen, ipcMain, dialog, shell} = require('electron');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const {fork} = require('child_process');
const {encryptFileInPlace, decryptFileInPlace} = require('./aes_encrypt');
const {lockFile, unlockFile} = require("../../helpers/lock_mechanism");
const exec = require("nodemon/lib/config/exec");
const isMac = process.platform === 'darwin';
let html_page = undefined;
let mainWindow = undefined;
let alertWindow = undefined;
let fetcherProcess = null;
let backupProcess = null;
let externalEndpointsProcess = null;
let sendFileProcess = null;
const createInitialKeys = () => {
const SECRET_KEY = crypto.randomBytes(32);
const IV = crypto.randomBytes(16);
const secretKeyPath = path.join(__dirname, '..', '..', 'secret.key');
const ivPath = path.join(__dirname, '..', '..', 'iv.key');
fs.writeFileSync(secretKeyPath, SECRET_KEY);
console.log(`Secret Key saved to ${secretKeyPath}`);
fs.writeFileSync(ivPath, IV);
console.log(`IV saved to ${ivPath}`);
}
const checkForServerConnection = async () => {
const pathToIpConfig = path.join(__dirname, '..', '..', 'ipConfig.json');
try {
console.log('Checking for server connection');
await fs.promises.access(pathToIpConfig);
await lockFile(pathToIpConfig);
await decryptFileInPlace(pathToIpConfig);
const ipConfig = await fs.promises.readFile(pathToIpConfig, 'utf-8');
const { ip } = JSON.parse(ipConfig);
const response = await fetch(`http://${ip}/heartbeat`);
await encryptFileInPlace(pathToIpConfig)
await unlockFile(pathToIpConfig);
return response.ok;
} catch (error) {
console.error("Error:", error);
try{
fs.unlinkSync(pathToIpConfig);
}
catch{
}
return false;
}
};
async function runStartupChecks() {
try {
await fs.promises.access(path.join(__dirname, '..', '..', 'secret.key'), fs.constants.F_OK);
await fs.promises.access(path.join(__dirname, '..', '..', 'iv.key'), fs.constants.F_OK);
} catch (err) {
console.error("Startup error detected:", err);
// Run the npm clean script
await exec('npm run clean', { cwd: path.join(__dirname, '..', '..') }, (error, stdout, stderr) => {
if (error) {
console.error('Error occurred while running npm run clean:', stderr);
return;
}
console.log('npm run clean output:', stdout);
createInitialKeys();
});
}
}
const createMainWindow = (async (title, width, height) => {
mainWindow = new BrowserWindow({
title: title,
width: width,
height: height,
resizable: false,
icon: path.join(__dirname, '..', 'renderer', 'assets', 'hard-disk.png'),
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
});
await runStartupChecks();
html_page = await checkForServerConnection() ? 'login.html' : 'ip_submit.html';
//mainWindow.setMenu(null);
mainWindow.loadFile(path.join(__dirname, '..', 'renderer', 'html', html_page))
.then(() => {
console.log('Main window loaded!')
})
.catch(err => console.error('Failed to load main window:', err));
});
const createAlertWindow = (title, width, height) => {
alertWindow = new BrowserWindow({
width: width,
height: height,
title: title,
icon: path.join(__dirname, '..', 'renderer', 'assets', 'hard-disk.png'),
resizable: false,
webPreferences: {
nodeIntegration: false,
preload: path.join(__dirname, 'preload.js')
}
});
//alertWindow.setMenu(null);
alertWindow.loadFile(path.join(__dirname, '..', 'renderer', 'html', 'alert_modal.html')).then(() => {
console.log('Alert window loaded!')
})
.catch(err => console.error('Failed to load alert window:', err));
alertWindow.on('closed', () => {
alertWindow = undefined;
});
}
function showAlert(message) {
if (alertWindow === undefined) {
const title = 'Alert';
const mainScreen = screen.getPrimaryDisplay();
const {width, height} = mainScreen.size;
createAlertWindow(title, width / 4, height / 4);
}
alertWindow.webContents.once('dom-ready', () => {
alertWindow.webContents.executeJavaScript(`showAlert("${message}")`);
});
}
app.whenReady().then(() => {
const title = "Application";
const mainScreen = screen.getPrimaryDisplay();
const {width, height} = mainScreen.size;
createMainWindow(title, width / 1.5, height / 1.5);
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createMainWindow(title, width, height);
}
});
});
app.on('before-quit', () => {
if (backupProcess !== null) {
backupProcess.kill();
}
if (externalEndpointsProcess !== null) {
externalEndpointsProcess.kill();
}
if (fetcherProcess !== null) {
fetcherProcess.kill();
}
if (sendFileProcess !== null) {
sendFileProcess.kill();
}
});
app.on('window-all-closed', () => {
if (!isMac) {
app.quit();
}
});
ipcMain.handle('write-file', async (event, fileName, content) => {
try {
let filePath = path.join(__dirname, '..', '..', fileName);
await lockFile(filePath);
await fs.promises.writeFile(filePath, content);
await encryptFileInPlace(filePath);
await unlockFile(filePath)
console.log(`File successfully written to ${filePath}`);
return {success: true};
} catch (error) {
console.error('Failed to write file:', error);
return {success: false, error: error.message};
}
});
ipcMain.handle('delete-file', async (event, fileName) => {
try {
let filePath = path.join(__dirname, '..', '..', fileName);
await fs.promises.unlink(filePath);
console.log(`File ${filePath} successfully deleted`);
return {success: true};
} catch (error) {
console.error('Failed to delete file:', error);
return {success: false, error: error.message};
}
});
ipcMain.handle('read-file', async (event, fileName) => {
try {
let filePath = path.join(__dirname, '..', '..', fileName);
await lockFile(filePath);
await decryptFileInPlace(filePath);
const content = await fs.promises.readFile(filePath, 'utf-8');
await encryptFileInPlace(filePath);
await unlockFile(filePath);
return {success: true, content};
} catch (error) {
console.error('Error reading file:', error);
return {success: false, error: error.message};
}
});
ipcMain.handle('change-content', async (event, nextPage) => {
try {
html_page = nextPage;
await mainWindow.loadFile(path.join(__dirname, '..', 'renderer', 'html', nextPage));
return true;
} catch (error) {
console.error('Error changing content:', error);
return false;
}
});
ipcMain.handle('open-dir-dialog', async (event) => {
try {
const result = await dialog.showOpenDialog({
properties: ['openDirectory'],
});
if (result.canceled || result.filePaths.length === 0) {
return {canceled: true}
}
return result.filePaths[0];
} catch (error) {
console.error('Error opening file dialog:', error);
return null;
}
})
ipcMain.handle('open-json-dir-config', async (event, fileName) => {
try {
const result = await dialog.showOpenDialog({
properties: ['openDirectory'],
});
if (result.canceled || result.filePaths.length === 0) {
return {canceled: true}
}
const dirPath = result.filePaths[0];
await fs.promises.writeFile(
path.join(__dirname, '..', '..', fileName),
JSON.stringify({
path: dirPath
}, null, 2));
return true;
} catch (error) {
console.error('Error opening file dialog:', error);
return {error: error.message};
}
});
ipcMain.handle('open-file-dialog', async (event) => {
const result = await dialog.showOpenDialog({
properties: ['openFile']
});
return result.filePaths[0] || '';
});
ipcMain.handle('check-file-exists', async (event, fileName) => {
try {
const filePath = path.join(__dirname, '..', '..', fileName);
return await fs.promises.access(filePath)
.then(() => true)
.catch(() => false);
} catch (error) {
console.error('Error checking file existence:', error);
throw error; // Propagate the error to the renderer process
}
});
ipcMain.handle('show-alert', async (event, message) => {
showAlert(message);
});
ipcMain.on('close-alert-window', () => {
if (alertWindow) {
alertWindow.close();
alertWindow = undefined;
}
});
//External processes
ipcMain.handle('start-main-processes', async (event, args) => {
if (!backupProcess) {
backupProcess = fork(path.join(__dirname, '..', '..', 'jobs', 'backup.js'), args, { silent: false });
backupProcess.on('exit', () => {
backupProcess = null;
});
backupProcess.on('error', (err) => {
console.log('Backup process error:', err);
});
}
if (!fetcherProcess) {
fetcherProcess = fork(path.join(__dirname, '..', '..', 'jobs', 'fetcher.js'), args, { silent: false });
fetcherProcess.on('exit', () => {
fetcherProcess = null;
});
fetcherProcess.on('error', (err) => {
console.log('Fetcher process error:', err);
});
fetcherProcess.on('message', (message) => {
if (message.type === 'startBackup') {
backupProcess.send({
type: 'startBackup'
});
}
});
}
if (!externalEndpointsProcess) {
externalEndpointsProcess = fork(path.join(__dirname, '..', '..', 'jobs', 'external_endpoints.js'), args, { silent: false });
externalEndpointsProcess.on('exit', () => {
externalEndpointsProcess = null;
});
externalEndpointsProcess.on('error', (err) => {
console.log('External endpoints process error:', err);
});
}
return true; // Indicate that the operation has started
});
ipcMain.handle('start-send-file-process', async (event, args) => {
if (sendFileProcess === null) {
sendFileProcess = fork(path.join(__dirname, '..', '..', 'jobs', 'send_file.js'), args, {silent: false});
sendFileProcess.on('exit', () => {
sendFileProcess = null;
});
}
return true; // Indicate that the operation has started
});
ipcMain.handle('kill-before-logout', async(event) =>{
if (backupProcess !== null) {
backupProcess.kill('SIGINT');
}
if (externalEndpointsProcess !== null) {
externalEndpointsProcess.kill('SIGINT');
}
if (fetcherProcess !== null) {
fetcherProcess.kill('SIGINT');
}
if (sendFileProcess !== null) {
sendFileProcess.kill('SIGINT');
}
})
ipcMain.handle('decrypt-backup-files', async(event, destPath) => {
console.log('ai intrat in handle')
if(backupProcess != null){
console.log('esti in process');
backupProcess.send({
type: 'decryptBackup',
decryptDestPath: destPath
});
}
})
// Handle showing a file in the system file explorer
ipcMain.handle('show-file-in-explorer', async (event, filePath) => {
try {
// Ensure the file exists before attempting to show it
await fs.promises.access(filePath, fs.constants.F_OK);
shell.showItemInFolder(filePath); // Opens the file explorer and highlights the file
return true;
} catch (error) {
console.error('File does not exist:', error);
return false
}
});
// Handle removing a path from 'filesReceived.json'
ipcMain.handle('remove-path-from-received-files', async (event, filePath) => {
try {
const jsonFilePath = path.join(__dirname, '..', '..', 'filesReceived.json');
await lockFile(jsonFilePath);
await decryptFileInPlace(jsonFilePath);
const data = await fs.promises.readFile(jsonFilePath, 'utf8');
const jsonData = JSON.parse(data);
// Filter out the specified file path
jsonData.receivedFiles = jsonData.receivedFiles.filter(file => file !== filePath);
await fs.promises.writeFile(jsonFilePath, JSON.stringify(jsonData, null, 2), 'utf8');
await encryptFileInPlace(jsonFilePath);
await unlockFile(jsonFilePath);
return true;
} catch (error) {
console.error('Error updating filesReceived.json:', error);
throw new Error('Failed to update received files list.');
}
});
+299
View File
@@ -0,0 +1,299 @@
import {app, BrowserWindow, ipcMain, IpcMainInvokeEvent} from 'electron';
import path from 'path';
import { promises as fs } from 'fs';
import dotenv from 'dotenv';
import {WorkerManager} from "../helpers/worker_manager";
import {DirectoryWatcher} from "../helpers/directory_watcher";
import {QueueManager} from "../helpers/queue_manager";
import {compareFnFileItemTask, FileItemTask} from "../interfaces/file_item_task";
import {TaskScheduler} from "../helpers/task_scheduler";
import {WindowManager} from "../helpers/window_manager";
import {JsonManager} from "../helpers/json_manager";
import {MemoryManager} from "../helpers/memory_manager";
import {TcpCommunicator} from "../helpers/tcp_communicator";
import {operationCodes} from "../network/operation_codes";
// Load environment variables
dotenv.config({ path: path.join(__dirname, '..', '..', '.env') });
const UDP_PORT = process.env.UDP_PORT ? parseInt(process.env.UDP_PORT) : 41233;
const TCP_PORT = process.env.TCP_PORT ? parseInt(process.env.TCP_PORT) : 41234;
const HOST = process.env.HOST || '0.0.0.0';
let mainWindow: BrowserWindow | null = null;
let windowManager: WindowManager | null = null;
let tcpCommunicator: TcpCommunicator | null = null;
let userConfig: JsonManager | null = null;
let applicationInfo: JsonManager | null = null;
let memoryManager: MemoryManager | null = null;
let workerManager: WorkerManager | null = null;
let taskScheduler: TaskScheduler | null = null;
let backupDirectoryManager: DirectoryWatcher | null = null;
let departmentShareManager: DirectoryWatcher | null = null;
let sendFileQueue: QueueManager<FileItemTask> | null = null;
const pathToPagesDir = path.join(__dirname, '..', '..', 'render', 'html');
const pathToWorkerDir = path.join(__dirname, '..', 'workers');
const pathToJsons = path.join(__dirname, '..', 'json_files');
const pathToClientsBackups = path.join(__dirname, '..', 'backups');
async function cleanupAndExit() {
// Stop all workers
if (workerManager) {
console.log('Terminating all workers...');
workerManager.closeAllWorkers();
}
// Reset memory
if (memoryManager) {
await memoryManager.resetFile();
}
// Close watchers
if (backupDirectoryManager) {
console.log('Stopping backup directory watcher...');
backupDirectoryManager.closeWatcher(); // Add this method to DirectoryWatcher to close the watcher
}
if (departmentShareManager) {
console.log('Stopping department directory watcher...');
departmentShareManager.closeWatcher(); // Add this method to DirectoryWatcher to close the watcher
}
if(taskScheduler){
console.log('Stopping all tasks...');
taskScheduler.stopAllTasks()
}
if(workerManager){
console.log('Terminating all workers...');
workerManager.closeAllWorkers()
}
console.log('Cleanup complete, exiting application.');
app.quit(); // This will properly close the application
}
async function ensureDirectoryExists(dirPath: string): Promise<void> {
try {
await fs.access(dirPath);
} catch (err) {
// If the directory doesn't exist, create it
await fs.mkdir(dirPath, { recursive: true });
console.log(`Directory created: ${dirPath}`);
}
}
app.whenReady().then(async () => {
const title = 'Application';
const mainScreen = require('electron').screen.getPrimaryDisplay();
const { width, height } = mainScreen.size;
mainWindow = new BrowserWindow({
title,
width: width / 1.5,
height: height / 1.5,
resizable: false,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
},
});
await ensureDirectoryExists(pathToJsons);
await ensureDirectoryExists(pathToClientsBackups);
windowManager = new WindowManager(mainWindow, pathToPagesDir);
userConfig = new JsonManager(path.join(pathToJsons, 'userConfig.json'));
applicationInfo = new JsonManager(path.join(pathToJsons, 'application.json'));
memoryManager = new MemoryManager(path.join(pathToJsons, 'memory.json'));
sendFileQueue = new QueueManager(path.join(pathToJsons, 'sendFileTasks.json'), compareFnFileItemTask);
taskScheduler = new TaskScheduler(applicationInfo, windowManager);
workerManager = new WorkerManager(pathToWorkerDir, windowManager);
await userConfig.writeValue('app_type', 'client');
await applicationInfo.writeValue('users_ip', []);
await memoryManager.resetFile();
workerManager.startWatchersWorker(path.join(pathToJsons, 'memory.json'), path.join(pathToJsons, 'application.json'));
workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT);
workerManager.startResourceCoordinatorWorker(
path.join(pathToJsons, 'userConfig.json'),
path.join(pathToJsons, 'application.json'),
path.join(pathToJsons, 'memory.json'),
path.join(pathToJsons, 'sendFileTasks.json'),
TCP_PORT
);
taskScheduler.startUCCheck(UDP_PORT, 'login', 'uc_not_found');
taskScheduler.startUserIPLookup(UDP_PORT);
registerIPCHandlers();
await windowManager.changeContent('welcome');
});
app.on('window-all-closed', async () => {
console.log('All windows closed, starting cleanup...');
await cleanupAndExit(); // Call cleanup when all windows are closed
});
// Catch CTRL+C (SIGINT) and clean up resources
process.on('SIGINT', async () => {
console.log('CTRL+C pressed, starting cleanup...');
await cleanupAndExit(); // Call cleanup on SIGINT
});
app.on('before-quit', async () => {
console.log('Application is quitting, starting cleanup...');
await cleanupAndExit(); // Call cleanup before app quit
});
// Register IPC handlers
function registerIPCHandlers() {
// Window Manager IPC Handlers
ipcMain.handle('show-alert', async (event: IpcMainInvokeEvent, message: string) => {
if (!windowManager) throw new Error('WindowManager is not initialized.');
await windowManager.showAlert(message);
});
ipcMain.handle('change-content', async (_event: IpcMainInvokeEvent, destination: string) => {
if (!windowManager) throw new Error('WindowManager is not initialized.');
await windowManager.changeContent(destination);
});
ipcMain.handle('select-directory', async (_event: IpcMainInvokeEvent) => {
if (!windowManager) throw new Error('WindowManager is not initialized.');
return await windowManager.selectDirectory();
});
ipcMain.handle('select-file', async (_event: IpcMainInvokeEvent) => {
if (!windowManager) throw new Error('WindowManager is not initialized.');
return await windowManager.selectFile();
});
ipcMain.handle('show-file-in-explorer', async (_event: IpcMainInvokeEvent, path: string) => {
if (!windowManager) throw new Error('WindowManager is not initialized.');
return await windowManager.showFileInExplorer(path);
});
// TcpMethods IPC Handlers
ipcMain.handle('open-socket', async (_event: IpcMainInvokeEvent) => {
if (!applicationInfo) throw new Error('TcpMethods is not initialized.');
const serverIp = await applicationInfo.readValue('serverIp');
if (!serverIp) return;
tcpCommunicator = new TcpCommunicator(serverIp, TCP_PORT);
return await tcpCommunicator.connect()
});
ipcMain.handle('send-message', async (_event: IpcMainInvokeEvent, operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer) => {
if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.');
return await tcpCommunicator.sendMessage(operationCode, metaInfo, fileContent);
});
ipcMain.handle('has-response-arrived', async (_event: IpcMainInvokeEvent) => {
if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.');
return tcpCommunicator.hasResponseArrived();
});
ipcMain.handle('close-socket', async (_event: IpcMainInvokeEvent) => {
if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.');
return await tcpCommunicator.disconnect();
});
ipcMain.handle('get-last-result', async (_event: IpcMainInvokeEvent) => {
if (!tcpCommunicator) throw new Error('TcpMethods is not initialized.');
return tcpCommunicator.getLastResult();
});
ipcMain.handle('get-operation-codes', () => {
return operationCodes;
});
// UserConfig IPC Handlers
ipcMain.handle('read-user-json-files', async (_event: IpcMainInvokeEvent, key: string) => {
if (!userConfig) throw new Error('UserConfig is not initialized.');
return await userConfig.readValue(key);
});
ipcMain.handle('write-user-json-files', async (_event: IpcMainInvokeEvent, key: string, value: any) => {
if (!userConfig) throw new Error('UserConfig is not initialized.');
return userConfig.writeValue(key, value);
});
ipcMain.handle('reset-user-json-files', async () => {
if (!userConfig) throw new Error('UserConfig is not initialized.');
return userConfig.resetFile();
});
ipcMain.handle('remove-user-json-files-key', async (_event: IpcMainInvokeEvent, key: string) => {
if (!userConfig) throw new Error('UserConfig is not initialized.');
return userConfig.removeValue(key);
});
// ApplicationPreferences IPC Handlers
ipcMain.handle('read-application-json-files', async (_event: IpcMainInvokeEvent, key: string) => {
if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.');
return await applicationInfo.readValue(key);
});
ipcMain.handle('write-application-json-files', async (_event: IpcMainInvokeEvent, key: string, value: any) => {
if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.');
return applicationInfo.writeValue(key, value);
});
ipcMain.handle('reset-application-json-files', async () => {
if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.');
return applicationInfo.resetFile();
});
ipcMain.handle('remove-application-json-files-key', async (_event: IpcMainInvokeEvent, key: string) => {
if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.');
return applicationInfo.removeValue(key);
});
// Memory IPC Handlers
ipcMain.handle('memory-create-entry', async () => {
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
return memoryManager.storeMetaInformation({});
});
ipcMain.handle('memory-read-entry', async (_event: IpcMainInvokeEvent, id: string) => {
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
return memoryManager.retrieveMetaInformation(id);
});
ipcMain.handle('memory-update-entry', async (_event: IpcMainInvokeEvent, id: string, data: any) => {
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
return memoryManager.updateMetaInformation(id, data);
});
ipcMain.handle('memory-remove-entry', async (_event: IpcMainInvokeEvent, id: string) => {
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
return memoryManager.removeMetaInformation(id);
});
// Queue IPC Handlers
ipcMain.handle('add-task-to-send-file-queue', async (event: IpcMainInvokeEvent, task: FileItemTask) => {
if (!sendFileQueue) throw new Error('SendFileQueue is not initialized.');
sendFileQueue.enqueue(task);
});
// BackupRetrievalWorker IPC Handler
ipcMain.handle('start-backup-retrieval', async (_event: IpcMainInvokeEvent, destinationPath: string) => {
if (!workerManager) throw new Error('WorkerManager is not initialized.');
return workerManager.startBackupRetrievalWorker(
path.join(pathToJsons, 'userConfig.json'),
path.join(pathToJsons, 'application.json'),
TCP_PORT,
destinationPath
);
});
}
-21
View File
@@ -1,21 +0,0 @@
const {contextBridge, ipcRenderer} = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
writeFile: (fileName, content) => ipcRenderer.invoke('write-file', fileName, content),
readFile: (fileName) => ipcRenderer.invoke('read-file', fileName),
deleteFile: (fileName) => ipcRenderer.invoke('delete-file', fileName),
changeContent: (nextPage) => ipcRenderer.invoke('change-content', nextPage),
showAlert: (message) => ipcRenderer.invoke('show-alert', message),
closeAlertWindow: () => ipcRenderer.send('close-alert-window'),
openJsonDirConfigDialog: (fileName) => ipcRenderer.invoke('open-json-dir-config', fileName),
openDirDialog: () => ipcRenderer.invoke('open-dir-dialog'),
openFileDialog: () => ipcRenderer.invoke('open-file-dialog'),
checkFileExists: (fileName) => ipcRenderer.invoke('check-file-exists', fileName),
startMainProcesses: async (args) => ipcRenderer.invoke('start-main-processes', args),
killBeforeLogout: async() => ipcRenderer.invoke('kill-before-logout'),
decryptFiles: async(destPath) => ipcRenderer.invoke('decrypt-backup-files', destPath),
showFileInExplorer: (filePath) => ipcRenderer.invoke('show-file-in-explorer', filePath),
removePathFromReceivedFiles: (filePath) => ipcRenderer.invoke('remove-path-from-received-files', filePath)
});
+43
View File
@@ -0,0 +1,43 @@
import { contextBridge, ipcRenderer } from 'electron';
import {FileItemTask} from "../interfaces/file_item_task";
contextBridge.exposeInMainWorld('electronAPI', {
// UserConfig methods
readUserConfig: (key: string): Promise<any> => ipcRenderer.invoke('read-user-json-files', key),
writeUserConfig: (key: string, value: any): Promise<boolean> => ipcRenderer.invoke('write-user-json-files', key, value),
removeUserConfig: (key: string) : Promise<boolean> => ipcRenderer.invoke('remove-user-json-files', key),
resetUserConfig: (): Promise<boolean> => ipcRenderer.invoke('reset-user-json-files'),
// ApplicationPreferences methods
readApplicationInfo: (key: string): Promise<any> => ipcRenderer.invoke('read-application-json-files', key),
writeApplicationInfo: (key: string, value: any): Promise<boolean> => ipcRenderer.invoke('write-application-json-files', key, value),
removeApplicationInfo: (key: string) : Promise<boolean> => ipcRenderer.invoke('remove-application-preferences', key),
resetApplicationInfo: (): Promise<boolean> => ipcRenderer.invoke('reset-application-json-files'),
// UcCommunication methods
openUcSocket: (): Promise<any> => ipcRenderer.invoke('open-socket'),
sendUcMessage: (operationCode: string, metaInfo: any, fileContent: any): Promise<any> => ipcRenderer.invoke('send-message', operationCode, metaInfo, fileContent),
closeUcSocket: (): Promise<any> => ipcRenderer.invoke('close-socket'),
hasResponseArrived: (): Promise<boolean> => ipcRenderer.invoke('has-response-arrived'),
getLastUcResult: (): Promise<any> => ipcRenderer.invoke('get-last-result'),
getOperationsCodes: (): Promise<{ data: { [key: string]: string } }> => ipcRenderer.invoke('get-operation-codes'),
// MemoryManager methods
createMemoryEntry: (): Promise<string> => ipcRenderer.invoke('memory-create-entry'),
readMemoryEntry: (id: string): Promise<any> => ipcRenderer.invoke('memory-read-entry', id),
updateMemoryEntry: (id: string, data: any): Promise<boolean> => ipcRenderer.invoke('memory-update-entry', id, data),
removeMemoryEntry: (id: string): Promise<boolean> => ipcRenderer.invoke('memory-remove-entry', id),
// UI methods
showAlert: (message: string): Promise<void> => ipcRenderer.invoke('show-alert', message),
changeContent: (destination: string): Promise<void> => ipcRenderer.invoke('change-content', destination),
selectDirectory: (): Promise<string | undefined> => ipcRenderer.invoke('select-directory'),
selectFile: (): Promise<string | undefined> => ipcRenderer.invoke('select-file'),
showFileInExplorer: (path: string): Promise<void> => ipcRenderer.invoke('show-file-in-explorer', path),
// Queue methods
addTaskToSendFileQueue: (task: FileItemTask): Promise<void> => ipcRenderer.invoke('add-task-to-send-file-queue', task),
// BackupRetrievalWorker
startBackupRetrieval: (destinationPath: string): Promise<void> => ipcRenderer.invoke('start-backup-retrieval', destinationPath)
});
+46
View File
@@ -0,0 +1,46 @@
import { SocketCommunicatorBase } from "./socket_communicator/socket_communicator_base";
interface Connection {
communicator: SocketCommunicatorBase;
}
export class ConnectionManager {
private readonly connections: { [key: string]: Connection };
constructor() {
this.connections = {};
}
// Adds a new communicator, keyed by both IP and port
addConnection(ip: string, port: number, communicator: SocketCommunicatorBase): void {
const key = `${ip}:${port}`;
// Store the communicator along with the client's public and private keys
this.connections[key] = {
communicator
};
console.log(`Added communicator for ${ip}:${port}, generated public and private keys.`);
}
// Removes a communicator based on IP and port
removeCommunicator(ip: string, port: number): void {
const key = `${ip}:${port}`;
if (this.connections[key]) {
delete this.connections[key];
console.log(`Removed communicator for ${ip}:${port}`);
}
}
// Retrieves a communicator based on IP and port
getCommunicator(ip: string, port: number): SocketCommunicatorBase | null {
const key = `${ip}:${port}`;
return this.connections[key] ? this.connections[key].communicator : null;
}
// Checks if a communicator exists for a given IP and port
communicatorExists(ip: string, port: number): boolean {
const key = `${ip}:${port}`;
return this.connections[key] !== undefined;
}
}
+65
View File
@@ -0,0 +1,65 @@
export interface ParsedMessage {
operationCode: string;
metaInfo?: { [key: string]: any };
fileContent?: Buffer;
}
export class MessageHandler {
// Format the message with operationCode, guid, metaInfo, and fileContent (Base64 for fileContent)
static formatMessage(
operationCode: string,
metaInfo?: { [key: string]: any },
fileContent?: Buffer
): string {
let message = `${operationCode}\n`; // First part: operationCode and guid
if (metaInfo && Object.keys(metaInfo).length > 0) {
message += `${JSON.stringify(metaInfo)}\n`; // Add metaInfo
}
if (fileContent && fileContent.length > 0) {
message += fileContent.toString('base64'); // Convert buffer to Base64 for fileContent
}
return message;
}
// Parse the incoming message (convert Base64 back to Buffer if fileContent is present)
static parseMessage(msg: string): ParsedMessage {
const parts = msg.split('\n'); // Split by \n (operationCode, metaInfo, and fileContent are on separate lines)
// First part should always be the operation code
const operationCode = parts[0]?.trim();
if (!operationCode) {
throw new Error('Missing operation code in the message');
}
let metaInfo: { [key: string]: any } | undefined = undefined;
let fileContent: Buffer | undefined = undefined;
// Parse the metaInfo (JSON object) if present
if (parts[1]) {
try {
metaInfo = JSON.parse(parts[1].trim());
} catch (err) {
console.error('Invalid metaInfo JSON format:', err);
}
}
// Convert Base64 string back to Buffer for fileContent if present
if (parts[2]) {
fileContent = Buffer.from(parts[2].trim(), 'base64');
}
return {
operationCode,
metaInfo,
fileContent,
};
}
// Validate if the parsed message contains an operation code
static validateMessage(parsedMessage: ParsedMessage | null): boolean {
return !!parsedMessage?.operationCode;
}
}
+2
View File
@@ -0,0 +1,2 @@
export { UdpClient} from './udp/udp_client';
export { TcpClient } from './tcp/tcp_client'
+39
View File
@@ -0,0 +1,39 @@
export let operationCodes = {
// General Operations
HEARTBEAT: 'HEARTBEAT',
ALIVE: 'ALIVE',
SET_PUBLIC_KEY: 'SET_PUBLIC_KEY',
SET_AES_KEY: 'SET_AES_KEY',
RESET_DATABASE: 'RESET_DATABASE',
OK: 'OK',
ERR: 'ERR',
END: 'END',
UNKNOWN_COMMAND: 'UNKNOWN_COMMAND',
// Auth Operations
LOGIN: 'LOGIN',
SIGN_UP: 'SIGN_UP',
RESET_PASSWORD: 'RESET_PASSWORD',
FIND_BY_EMAIL: 'FIND_BY_EMAIL',
MODIFY_USER: 'MODIFY_USER',
GET_DEPARTMENTS: 'GET_DEPARTMENTS',
CREATE_DEPARTMENT: 'CREATE_DEPARTMENT',
MODIFY_DEPARTMENT: 'MODIFY_DEPARTMENT',
DELETE_DEPARTMENT: 'DELETE_DEPARTMENT',
GET_USERS: 'GET_USERS',
FIND_KEY_BY_USER_ID: 'FIND_KEY_BY_USER_ID',
GET_USER_INFORMATION: 'GET_USER_INFORMATION',
CLEAR_BACKUP: 'CLEAR_BACKUP',
BACKUP_FILE: 'BACKUP_FILE',
SHARE_FILE: 'SHARE_FILE',
CLEAR_DEPARTMENT: 'CLEAR_DEPARTMENT',
DEPARTMENT_FILE: 'DEPARTMENT_FILE',
IS_BACKUP_CREATED: 'IS_BACKUP_CREATED',
GET_BACKUP_STRUCTURE: 'GET_BACKUP_STRUCTURE',
REQ_FILE_FROM_BACKUP: 'REQ_FILE_FROM_BACKUP',
DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE',
};
@@ -0,0 +1,43 @@
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 {
console.log('OK operation received');
return parsedMessage; // Typically, you would just acknowledge with OK, returning as-is
}
// Default handler for ERR operation
public static handleErr(parsedMessage: ParsedMessage): ParsedMessage {
console.log('ERR operation received: ', parsedMessage.metaInfo?.message || 'No error details provided');
return parsedMessage; // Typically, you would log the error and return
}
// Default handler for END operation
public static handleEnd(parsedMessage: ParsedMessage): ParsedMessage {
console.log('END operation received');
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;
}
@@ -0,0 +1,58 @@
// operation_handler.ts
import { ParsedMessage, MessageHandler } from '../message_handler';
import { OperationPlugin } from './operation_plugin';
type OperationHandlerFunction = (parsedMessage: ParsedMessage) => ParsedMessage;
export class OperationHandler {
private static instance: OperationHandler;
private handlers: { [operationCode: string]: OperationHandlerFunction } = {};
private constructor() {
// Register only the unknown command handler on initialization
this.registerHandler('UNKNOWN_COMMAND', this.handleUnknownCommand);
}
// Singleton instance
public static getInstance(): OperationHandler {
if (!OperationHandler.instance) {
OperationHandler.instance = new OperationHandler();
}
return OperationHandler.instance;
}
// Register a handler for a specific operation code
public registerHandler(operationCode: string, handler: OperationHandlerFunction): void {
this.handlers[operationCode] = handler;
}
// Handle operation request
public handleOperation(rawMessage: string): ParsedMessage {
// Parse and validate the message
const parsedMessage = MessageHandler.parseMessage(rawMessage);
if (!parsedMessage || !MessageHandler.validateMessage(parsedMessage)) {
return this.handleUnknownCommand(parsedMessage);
}
// Dispatch the handler for the given operation code
const handler = this.handlers[parsedMessage.operationCode];
if (handler) {
return handler(parsedMessage);
} else {
return this.handleUnknownCommand(parsedMessage);
}
}
// Default handler for unknown commands
private handleUnknownCommand(parsedMessage: ParsedMessage): ParsedMessage {
return {
operationCode: 'UNKNOWN_COMMAND',
metaInfo: { message: 'Unknown command received.' },
};
}
// Plugin system: Load plugins to register handlers
public loadPlugin(plugin: OperationPlugin): void {
plugin.register(this);
}
}
@@ -0,0 +1,6 @@
// operation_plugin.ts
import { OperationHandler } from './operation_handler';
export interface OperationPlugin {
register(operationHandler: OperationHandler): void;
}
@@ -0,0 +1,80 @@
import { ParsedMessage } from '../message_handler';
import { OperationBase } from '../operations_base/operation_base';
import { OperationHandler } from '../operations_base/operation_handler';
import os from 'node:os';
export class GeneralOperations extends OperationBase {
public static readonly operationCodes = {
...OperationBase.operationCodes, // Inherit common operation codes (OK, ERR, END)
HEARTBEAT: 'HEARTBEAT',
ALIVE: 'ALIVE',
SET_PUBLIC_KEY: 'SET_PUBLIC_KEY',
SET_AES_KEY: 'SET_AES_KEY',
};
// Handle heartbeat operation
public static handleHeartbeat(): ParsedMessage {
const networkInterfaces = os.networkInterfaces();
let ipAddress = 'Unknown';
for (const iface of Object.values(networkInterfaces)) {
// @ts-ignore
for (const address of iface) {
if (address.family === 'IPv4' && !address.internal) {
ipAddress = address.address;
break;
}
}
if (ipAddress !== 'Unknown') break;
}
return {
operationCode: GeneralOperations.operationCodes.ALIVE,
metaInfo: { ipAddress },
};
}
// Handle public key exchange
public static handlePublicKey(parsedMessage: ParsedMessage): ParsedMessage {
const clientPublicKey = parsedMessage.metaInfo?.publicKey;
if (clientPublicKey) {
return {
operationCode: GeneralOperations.operationCodes.SET_PUBLIC_KEY,
metaInfo: { publicKey: clientPublicKey },
};
} else {
return {
operationCode: GeneralOperations.operationCodes.ERR,
metaInfo: { message: 'No public key provided.' },
};
}
}
// Handle AES key exchange
public static handleAESKey(parsedMessage: ParsedMessage): ParsedMessage {
const aesKey = parsedMessage.metaInfo?.aesKey;
const aesIv = parsedMessage.metaInfo?.aesIv;
if (aesKey && aesIv) {
return {
operationCode: GeneralOperations.operationCodes.SET_AES_KEY,
metaInfo: { aesKey: aesKey, aesIv: aesIv },
};
} else {
return {
operationCode: GeneralOperations.operationCodes.ERR,
metaInfo: { message: 'No AES key provided.' },
};
}
}
// Register general operations with the OperationHandler
public register(operationHandler: OperationHandler): void {
// Register specific handlers for the general operations
operationHandler.registerHandler(GeneralOperations.operationCodes.HEARTBEAT, GeneralOperations.handleHeartbeat);
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_PUBLIC_KEY, GeneralOperations.handlePublicKey);
operationHandler.registerHandler(GeneralOperations.operationCodes.SET_AES_KEY, GeneralOperations.handleAESKey); // Register AES key handler
// Register common operations inherited from the base class
OperationBase.registerCommonOperations(operationHandler);
}
}
@@ -0,0 +1,500 @@
import { ParsedMessage } from '../message_handler';
import { OperationBase } from '../operations_base/operation_base';
import { OperationHandler } from '../operations_base/operation_handler';
import path from 'path';
import fs from 'fs';
import { FileEncryptor } from '../../helpers/file_encryptor';
const LOCK_FILE_EXTENSION = '.lock';
export class UserToUserOperations extends OperationBase {
public static readonly operationCodes = {
...OperationBase.operationCodes, // Inherit common operation codes (OK, ERR, END)
GET_USER_INFORMATION: 'GET_USER_INFORMATION',
BACKUP_FILE: 'BACKUP_FILE',
CLEAR_BACKUP: 'CLEAR_BACKUP',
SHARE_FILE: 'SHARE_FILE',
CLEAR_DEPARTMENT: 'CLEAR_DEPARTMENT',
DEPARTMENT_FILE: 'DEPARTMENT_FILE',
IS_BACKUP_CREATED: 'IS_BACKUP_CREATED',
GET_BACKUP_STRUCTURE: 'GET_BACKUP_STRUCTURE',
REQ_FILE_FROM_BACKUP: 'REQ_FILE_FROM_BACKUP',
DECRYPT_AND_SAVE_OLD_BACKUP_FILE: 'DECRYPT_AND_SAVE_OLD_BACKUP_FILE',
};
// Utility function to pause execution (sleep)
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 {
// 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);
}
// 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
}
}
// Handle user information retrieval
public static handleGetUserInformation(parsedMessage: ParsedMessage): ParsedMessage {
const pathToUserJson = path.join(__dirname, '..', '..', 'json_files', 'userConfig.json');
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;
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 {
// Ensure the directory structure exists (create directories if they don't exist)
const dirPath = path.dirname(fullFilePath);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
// Write the file content to the correct path
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) {
console.error(`Error saving file: ${error.message}`);
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error saving file: ${error.message}` },
};
}
}
// Handle clearing all backups for a user
public static handleClearBackup(parsedMessage: ParsedMessage): ParsedMessage {
// Ensure the userName is available in metaInfo
if (!parsedMessage.metaInfo || !parsedMessage.metaInfo.userName) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing user name in meta information.' },
};
}
const { userName } = parsedMessage.metaInfo;
// Base directory where backups are stored
const baseBackupDir = path.join(__dirname, '..', '..', 'backups');
const userBackupDir = path.join(baseBackupDir, userName);
try {
// Check if the user's backup directory exists
if (fs.existsSync(userBackupDir)) {
// Recursively delete the user's backup directory
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) {
console.error(`Error clearing backup for user: ${userName} - ${error.message}`);
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error clearing backup for user: ${userName}` },
};
}
}
// Handle sharing file operation
public static handleShareFile(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;
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 {
// Ensure the directory structure exists (create directories if they don't exist)
const dirPath = path.dirname(fullFilePath);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
// Write the file content to the correct path
fs.writeFileSync(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64');
console.log(`File shared successfully: ${fullFilePath}`);
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { message: `File shared successfully: ${relativeFilePath}` },
};
} catch (error: any) {
console.error(`Error sharing file: ${error.message}`);
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error sharing file: ${error.message}` },
};
}
}
public static handleClearDepartment(parsedMessage: ParsedMessage): ParsedMessage {
// Ensure the userName is available in metaInfo
if (!parsedMessage.metaInfo || !parsedMessage.metaInfo.userName) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing user name in meta information.' },
};
}
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, userName);
try {
// Check if the user's backup directory exists
if (fs.existsSync(userDepartmentDir)) {
// Recursively delete the user's backup directory
fs.rmSync(userDepartmentDir, { 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) {
console.error(`Error clearing backup for user: ${userName} - ${error.message}`);
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error clearing backup for user: ${userName}`},
};
}
}
public static handleDepartmentFile(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;
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.departmentDirectory) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Error retrieving share directory from application.json.' },
};
}
// Get the share directory path
const departmentDirectory = appInfo.departmentDirectory.path;
// Full path where the file will be stored (under the user's directory in the shared folder)
const fullFilePath = path.join(departmentDirectory, userName, relativeFilePath);
try {
// Ensure the directory structure exists (create directories if they don't exist)
const dirPath = path.dirname(fullFilePath);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
// Write the file content to the correct path
fs.writeFileSync(fullFilePath, Buffer.from(parsedMessage.fileContent as Buffer), 'base64');
console.log(`File shared successfully: ${fullFilePath}`);
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { message: `File shared successfully: ${relativeFilePath}`},
};
} catch (error: any) {
console.error(`Error sharing file: ${error.message}`);
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: {message: `Error sharing file: ${error.message}`},
};
}
}
// Check if a backup has been created for a user
public static handleIsBackupCreated(parsedMessage: ParsedMessage): ParsedMessage {
if(!parsedMessage.metaInfo) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing metaInfo.' },
};
}
const {name} = parsedMessage.metaInfo;
if (!name) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing name in meta information.' },
};
}
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 handleGetBackupStructure(parsedMessage: ParsedMessage): ParsedMessage {
if(!parsedMessage.metaInfo) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing metaInfo.' },
};
}
const {name} = parsedMessage.metaInfo;
if (!name) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing name in meta information.' },
};
}
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 buildDirectoryStructure(directoryPath: string): any {
const structure: any = {};
const files = fs.readdirSync(directoryPath);
for (const file of files) {
const filePath = path.join(directoryPath, file);
const stats = fs.statSync(filePath);
if (stats.isDirectory()) {
structure[file] = UserToUserOperations.buildDirectoryStructure(filePath); // Recursive for subdirectories
} else {
structure[file] = path.relative(directoryPath, filePath);
}
}
return structure;
}
// Handle file request from backup directory
public static handleReqFileFromBackup(parsedMessage: ParsedMessage): ParsedMessage {
if(!parsedMessage.metaInfo) {
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: 'Missing metaInfo.' },
};
}
const { name, relativeFilePath } = parsedMessage.metaInfo;
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 {
const fileContent = fs.readFileSync(fullFilePath, 'base64');
return {
operationCode: UserToUserOperations.operationCodes.OK,
metaInfo: { relativeFilePath },
fileContent: Buffer.from(fileContent, 'base64')
};
} catch (error: any) {
console.error(`Error reading file: ${error.message}`);
return {
operationCode: UserToUserOperations.operationCodes.ERR,
metaInfo: { message: `Error reading file: ${error.message}`},
};
}
}
// Register user-to-user operations with the OperationHandler
public register(operationHandler: OperationHandler): void {
// Register specific handlers for user-to-user operations
operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_USER_INFORMATION, UserToUserOperations.handleGetUserInformation);
operationHandler.registerHandler(UserToUserOperations.operationCodes.BACKUP_FILE, UserToUserOperations.handleBackupFile);
operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_BACKUP, UserToUserOperations.handleClearBackup);
operationHandler.registerHandler(UserToUserOperations.operationCodes.SHARE_FILE, UserToUserOperations.handleShareFile);
operationHandler.registerHandler(UserToUserOperations.operationCodes.DEPARTMENT_FILE, UserToUserOperations.handleDepartmentFile);
operationHandler.registerHandler(UserToUserOperations.operationCodes.CLEAR_DEPARTMENT, UserToUserOperations.handleClearDepartment);
operationHandler.registerHandler(UserToUserOperations.operationCodes.IS_BACKUP_CREATED, UserToUserOperations.handleIsBackupCreated);
operationHandler.registerHandler(UserToUserOperations.operationCodes.GET_BACKUP_STRUCTURE, UserToUserOperations.handleGetBackupStructure);
operationHandler.registerHandler(UserToUserOperations.operationCodes.REQ_FILE_FROM_BACKUP, UserToUserOperations.handleReqFileFromBackup);
// Register common operations inherited from the base class
OperationBase.registerCommonOperations(operationHandler);
}
}
@@ -0,0 +1,21 @@
import { ParsedMessage } from '../message_handler';
import { OperationHandler } from '../operations_base/operation_handler';
export abstract class SocketCommunicatorBase {
protected readonly ip: string;
protected readonly port: number;
protected readonly operationHandler: OperationHandler;
protected handlerResult: ParsedMessage | null;
protected constructor(ip: string, port: number, operationHandler: OperationHandler) {
this.ip = ip;
this.port = port;
this.operationHandler = operationHandler
this.handlerResult = null;
}
// Getter for the handler result
getHandlerResult(): ParsedMessage | null {
return this.handlerResult;
}
}
@@ -0,0 +1,167 @@
import { Socket } from 'net';
import { createCipheriv, createDecipheriv } from 'crypto';
import { MessageHandler, ParsedMessage } from '../message_handler';
import { SocketCommunicatorBase } from './socket_communicator_base';
import { OperationHandler } from '../operations_base/operation_handler';
import {constants, publicDecrypt} from "node:crypto";
import {operationCodes} from "../operation_codes";
const END_OF_MESSAGE = '<EOM>'; // Define a unique marker for end of message
export class TcpClientCommunicator extends SocketCommunicatorBase {
private readonly socket: Socket;
private aesKey: Buffer | null;
private aesIv: Buffer | null;
private messageBuffer: string;
private serverPublicKey: string | null;
private isAesKeySetFlag: boolean;
constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) {
super(ip, port, operationHandler);
this.socket = socket;
this.aesKey = null;
this.aesIv = null;
this.serverPublicKey = null;
this.messageBuffer = ''; // Buffer for message reassembly
this.isAesKeySetFlag = false;
}
setServerPublicKey(publicKey: string): void {
this.serverPublicKey = publicKey;
console.log('Server public key set.');
}
// Set the AES key when received
setAesKey(aesKey: string, aesIv: string): void {
this.aesKey = Buffer.from(aesKey, 'base64');
this.aesIv = Buffer.from(aesIv, 'base64');
console.log('AES key set.');
}
// Encrypt a message with AES
private encryptWithAes(message: string): string {
if (!this.aesKey || !this.aesIv) {
throw new Error('AES key or IV is not set.');
}
const cipher = createCipheriv('aes-256-cbc', this.aesKey, this.aesIv);
let encrypted = cipher.update(message, 'utf-8');
encrypted = Buffer.concat([encrypted, cipher.final()]);
return encrypted.toString('base64');
}
private decryptWithAes(encryptedMessage: string): string {
if (!this.aesKey || !this.aesIv) {
throw new Error('AES key or IV is not set.');
}
const decipher = createDecipheriv('aes-256-cbc', this.aesKey, this.aesIv);
let decrypted = decipher.update(Buffer.from(encryptedMessage, 'base64'));
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString('utf-8');
}
private decryptWithRsa(message: string): string {
if (!this.serverPublicKey) {
throw new Error('Server public key not set.');
}
try {
const encryptedMessage = Buffer.from(message.toString(), 'base64');
// Decrypt the message using the server's public key
const decrypted = publicDecrypt(
{
key: this.serverPublicKey,
padding: constants.RSA_PKCS1_PADDING, // Matching padding for decryption
},
encryptedMessage
);
return decrypted.toString('utf-8');
} catch (error) {
console.error('RSA decryption failed:', error);
throw new Error('Failed to decrypt RSA message.');
}
}
// Handle incoming chunks of data
async handleIncomingChunk(data: Buffer): Promise<void> {
const incomingMessage = data.toString();
this.messageBuffer += incomingMessage;
// Check if the message ends with END_OF_MESSAGE
if (this.messageBuffer.endsWith(END_OF_MESSAGE)) {
// Remove the END_OF_MESSAGE marker and process the message
const completeMessage = this.messageBuffer.slice(0, -END_OF_MESSAGE.length);
this.handleIncomingMessage(completeMessage);
// Clear the message buffer after processing
this.messageBuffer = '';
}
}
// Send a chunked message over the socket
async sendChunkedMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise<void> {
const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent);
let outgoingMessage: string;
// Encrypt the message with AES if available
if (this.aesKey && this.aesIv) {
outgoingMessage = this.encryptWithAes(message);
} else {
outgoingMessage = message // Send plain text if AES is not set
}
// Append the end marker to the message
outgoingMessage += END_OF_MESSAGE;
await this.writeToSocket(outgoingMessage);
}
// Write message to socket
private writeToSocket(message: string): Promise<void> {
return new Promise((resolve, reject) => {
this.socket.write(message, (err: any) => {
if (err) {
console.error('Error sending message over TCP:', err);
return reject(err);
}
resolve();
});
});
}
// Handle incoming message (decrypted if AES is set)
handleIncomingMessage(incomingMessage: string): void {
let messageToProcess = incomingMessage;
if (this.aesKey && this.aesIv) {
messageToProcess = this.decryptWithAes(incomingMessage);
}else if(this.serverPublicKey){
messageToProcess = this.decryptWithRsa(incomingMessage);
}
console.log(`\n\nComplete Message:\n${messageToProcess}\n\n`);
const result = this.operationHandler.handleOperation(messageToProcess);
if(result.operationCode === operationCodes.SET_AES_KEY){
this.isAesKeySetFlag = true;
this.setAesKey(result.metaInfo?.aesKey, result.metaInfo?.aesIv);
return;
}
if(result.operationCode === operationCodes.SET_PUBLIC_KEY) {
this.setServerPublicKey(result.metaInfo?.publicKey);
return;
}
this.handlerResult = result
}
// Check if AES key is set
isAesKeySet(): boolean {
return this.isAesKeySetFlag;
}
// Get handler result for operation handling
getHandlerResult(): ParsedMessage | null {
return this.handlerResult;
}
}
@@ -0,0 +1,161 @@
import { Socket } from 'net';
import { privateEncrypt, privateDecrypt, generateKeyPairSync, createCipheriv, createDecipheriv, randomBytes } from 'crypto';
import { MessageHandler, ParsedMessage } from '../message_handler';
import { SocketCommunicatorBase } from './socket_communicator_base';
import { OperationHandler } from '../operations_base/operation_handler';
import {constants} from "node:crypto";
const END_OF_MESSAGE = '<EOM>'; // Define a unique marker for end of message
export class TcpServerCommunicator extends SocketCommunicatorBase {
private readonly socket: Socket;
private privateKey: string | null;
private publicKey: string | null;
private aesKey: Buffer | null;
private aesIv: Buffer | null;
private messageBuffer: string;
constructor(socket: Socket, ip: string, port: number, operationHandler: OperationHandler) {
super(ip, port, operationHandler);
this.socket = socket;
this.privateKey = null;
this.publicKey = null; // Client public key will be set later
this.aesKey = null;
this.aesIv = null;
this.messageBuffer = ''; // Initialize the message buffer
this.generateKeyPair(); // Generate RSA key pair for encryption
}
// Generate RSA key pair (public and private keys)
generateKeyPair(): void {
const { privateKey, publicKey } = generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
});
this.privateKey = privateKey;
this.publicKey = publicKey;
console.log('RSA key pair generated.');
}
// Send the server's public key to the client
async sendPublicKey(): Promise<void> {
if (!this.publicKey) {
throw new Error('Public key is not available. Please generate RSA key pair.');
}
const message = MessageHandler.formatMessage('SET_PUBLIC_KEY', { publicKey: this.publicKey });
await this.writeToSocket(message + END_OF_MESSAGE);
console.log('Public key sent to client.');
}
// Generate AES key and IV, then send them to the client
async sendAesKey(): Promise<void> {
this.aesKey = randomBytes(32); // 256-bit AES key
this.aesIv = randomBytes(16); // AES IV
const aesKeyBase64 = this.aesKey.toString('base64');
const aesIvBase64 = this.aesIv.toString('base64');
const formattedMessage = MessageHandler.formatMessage('SET_AES_KEY', { aesKey: aesKeyBase64, aesIv: aesIvBase64 });
const encryptedMessage = this.encryptWithRsa(Buffer.from(formattedMessage)).toString('base64');
await this.writeToSocket(encryptedMessage + END_OF_MESSAGE);
console.log('AES key and IV sent to client.');
}
// Encrypt a message with the server's private key (RSA encryption)
private encryptWithRsa(message: Buffer): Buffer {
const bufferMessage = Buffer.isBuffer(message) ? message : Buffer.from(message);
if (!this.privateKey) throw new Error('Server private key not set.');
return privateEncrypt(
{
key: this.privateKey,
padding: constants.RSA_PKCS1_PADDING, // PKCS1 padding
},
bufferMessage
);
}
// Decrypt AES-encrypted messages
private decryptWithAes(encryptedMessage: string): string {
if (!this.aesKey || !this.aesIv) {
throw new Error('AES key not set.');
}
const decipher = createDecipheriv('aes-256-cbc', this.aesKey, this.aesIv);
let decrypted = decipher.update(Buffer.from(encryptedMessage, 'base64'));
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString('utf-8');
}
// Encrypt a message with AES
private encryptWithAes(message: string): string {
if (!this.aesKey || !this.aesIv) {
throw new Error('AES key or IV is not set.');
}
const cipher = createCipheriv('aes-256-cbc', this.aesKey, this.aesIv);
let encrypted = cipher.update(message, 'utf-8');
encrypted = Buffer.concat([encrypted, cipher.final()]);
return encrypted.toString('base64');
}
// Handle incoming chunks of data
async handleIncomingChunk(data: Buffer): Promise<void> {
const incomingMessage = data.toString();
this.messageBuffer += incomingMessage;
// Check if the message ends with END_OF_MESSAGE
if (this.messageBuffer.endsWith(END_OF_MESSAGE)) {
// Remove the END_OF_MESSAGE marker and process the message
const completeMessage = this.messageBuffer.slice(0, -END_OF_MESSAGE.length);
console.log(`\n\nComplete Message:\n${completeMessage}\n\n`);
this.handleIncomingMessage(completeMessage);
// Clear the message buffer after processing
this.messageBuffer = '';
}
}
// Send chunked message
async sendChunkedMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise<void> {
const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent);
let outgoingMessage: string;
if (this.aesKey && this.aesIv) {
outgoingMessage = this.encryptWithAes(message);
} else {
outgoingMessage = message;
}
outgoingMessage += END_OF_MESSAGE; // Append end-of-message marker
await this.writeToSocket(outgoingMessage);
}
// Handle incoming message (decrypt with AES if available)
handleIncomingMessage(incomingMessage: string): void {
let messageToProcess = incomingMessage;
if (this.aesKey && this.aesIv) {
messageToProcess = this.decryptWithAes(incomingMessage);
}
this.handlerResult = this.operationHandler.handleOperation(messageToProcess);
}
// Write message to socket
private writeToSocket(message: string): Promise<void> {
return new Promise((resolve, reject) => {
this.socket.write(message, (err: any) => {
if (err) {
console.error('Error sending message over TCP:', err);
return reject(err);
}
resolve();
});
});
}
}
@@ -0,0 +1,39 @@
import { Socket as UdpSocket } from 'dgram';
import { MessageHandler } from '../message_handler';
import { SocketCommunicatorBase } from './socket_communicator_base';
import {OperationHandler} from "../operations_base/operation_handler";
export class UdpSocketCommunicator extends SocketCommunicatorBase {
private readonly socket: UdpSocket;
constructor(socket: UdpSocket, ip: string, port: number, operationHandler: OperationHandler) {
super(ip, port, operationHandler);
this.socket = socket;
}
// Handle incoming message (no decryption needed for UDP)
handleIncomingMessage(incomingMessage: string): void {
this.handlerResult = this.operationHandler.handleOperation(incomingMessage);
}
// Send plain message over UDP (metaInfo as JSON and fileContent as Buffer)
async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise<void> {
const message = MessageHandler.formatMessage(operationCode, metaInfo, fileContent);
await this.sendUdpMessage(message);
}
// Helper method to wrap socket.send in a Promise for async/await support
private sendUdpMessage(message: string): Promise<void> {
return new Promise((resolve, reject) => {
this.socket.send(message, this.port, this.ip, (err: any) => {
if (err) {
console.error('Error sending UDP message:', err);
return reject(err);
}
console.log(`Plain message sent to ${this.ip}:${this.port} (UDP)`);
resolve();
});
});
}
}
+100
View File
@@ -0,0 +1,100 @@
import net, { Socket } from 'net';
import { TcpClientCommunicator } from '../socket_communicator/tcp_client_communicator';
import { OperationHandler } from '../operations_base/operation_handler';
import { operationCodes } from "../operation_codes";
import { GeneralOperations } from "../operations_custom/general_operations";
import { UserToUserOperations } from "../operations_custom/user_to_user_operations";
import { ParsedMessage } from "../message_handler";
export class TcpClient {
private readonly tcp_port: number;
private socket: Socket | null;
private communicator: TcpClientCommunicator | null;
private readonly operationHandler: OperationHandler;
private lastResult: ParsedMessage | null;
constructor(tcp_port: number) {
this.tcp_port = tcp_port;
this.socket = null;
this.communicator = null;
this.operationHandler = OperationHandler.getInstance();
this.lastResult = null;
this.operationHandler.loadPlugin(new GeneralOperations());
this.operationHandler.loadPlugin(new UserToUserOperations());
}
// Open a TCP socket connection
openSocket(ip: string): void {
this.socket = new net.Socket();
this.socket.connect(this.tcp_port, ip, () => {
console.log(`Client connected to server at ${ip}:${this.tcp_port}`);
this.communicator = new TcpClientCommunicator(this.socket as Socket, ip, this.tcp_port, this.operationHandler);
});
this.socket.on('error', (err) => {
console.error(`Connection error to server at ${ip}:${this.tcp_port}: ${err.message}`);
});
this.socket.on('data', async (data: Buffer) => {
if (this.communicator) {
await this.communicator.handleIncomingChunk(data); // Let the communicator handle the chunks
this.lastResult = this.communicator.getHandlerResult();
}
});
this.socket.on('close', () => {
console.log(`Connection closed: ${ip}:${this.tcp_port}`);
this.lastResult = null; // Clear the last result on socket close
});
}
// Close the socket connection
closeSocket(): void {
if (this.socket) {
this.socket.end();
this.socket = null;
this.communicator = null;
this.lastResult = null; // Clear the last result on close
console.log('Client socket connection closed.');
}
}
// Send a message with operationCode, metaInfo, and fileContent in chunks
async sendMessage(operationCode: string, metaInfo?: { [key: string]: any }, fileContent?: Buffer): Promise<boolean> {
if (!this.communicator || !this.isAesKeySet()) {
console.error('Communicator not initialized or AES key not set.');
return false;
}
await this.communicator.sendChunkedMessage(operationCode, metaInfo, fileContent); // Send message via communicator
return true;
}
// Check if AES key is set
isAesKeySet(): boolean {
if(!this.communicator) return false;
return this.communicator?.isAesKeySet()
}
// Check if the message is received (based on if lastResult is available)
isMessageReceived(): boolean {
console.log('Is message received?');
console.log(this.lastResult);
console.log(this.lastResult !== null);
return this.lastResult !== null;
}
// Get the last result (and clear it after returning)
getLastResult(): ParsedMessage | null {
const result = this.lastResult;
this.lastResult = null;
return result;
}
// Check if the socket is still connected
isSocketConnected(): boolean {
return this.socket !== null && !this.socket.destroyed;
}
}
+111
View File
@@ -0,0 +1,111 @@
import net, { Socket } from 'net';
import path from 'path';
import dotenv from 'dotenv';
import { ConnectionManager } from "../connection_manager";
import { TcpServerCommunicator } from "../socket_communicator/tcp_server_communicator";
import { GeneralOperations } from "../operations_custom/general_operations";
import { OperationHandler } from "../operations_base/operation_handler";
import { UserToUserOperations } from "../operations_custom/user_to_user_operations";
dotenv.config({ path: path.resolve(__dirname, './config/.env') });
export class TcpServer {
private readonly connectionManager: ConnectionManager;
private readonly operationHandler: OperationHandler;
private readonly port: number;
private readonly host: string;
constructor(host: string, port: number) {
this.connectionManager = new ConnectionManager();
this.operationHandler = OperationHandler.getInstance();
this.host = host;
this.port = port;
this.operationHandler.loadPlugin(new GeneralOperations());
this.operationHandler.loadPlugin(new UserToUserOperations());
}
// Start the TCP server
public start(): void {
const tcpServer = net.createServer();
// Handle incoming connections
tcpServer.on('connection', (socket: Socket) => {
const ip = socket.remoteAddress || 'unknown';
const port = socket.remotePort || 0;
const clientId = `${ip}:${port}`; // Use IP and port to identify the client
console.log(`Client connected: ${clientId}`);
const tcpCommunicator = new TcpServerCommunicator(socket, ip, port, this.operationHandler);
this.connectionManager.addConnection(ip, port, tcpCommunicator);
// Generate RSA key pair and start key exchange
tcpCommunicator.generateKeyPair();
tcpCommunicator.sendPublicKey()
.then(() => tcpCommunicator.sendAesKey())
.then(() => console.log('Public key and AES key sent successfully.'))
.catch(err => {
console.error(`Error during key exchange with client ${clientId}:`, err);
socket.end(); // Close the connection in case of any error
});
// Handle incoming data in chunks
socket.on('data', async (data: Buffer) => {
await this.handleData(data, ip, port);
});
// Handle client disconnect
socket.on('end', () => {
console.log(`Client disconnected: ${clientId}`);
this.connectionManager.removeCommunicator(ip, port);
});
// Handle socket errors
socket.on('error', (err: Error) => {
console.error(`Error from client ${clientId}: ${err.message}`);
this.connectionManager.removeCommunicator(ip, port);
});
});
// Handle server errors
tcpServer.on('error', (err: Error) => {
console.error(`TCP server error: ${err.message}`);
});
// Start listening for connections
tcpServer.listen(this.port, this.host, () => {
console.log(`TCP server listening on ${this.host}:${this.port}`);
});
}
// Handle incoming data from a client
private async handleData(data: Buffer, ip: string, port: number): Promise<void> {
const clientId = `${ip}:${port}`;
// Retrieve the communicator associated with this connection
const communicator = this.connectionManager.getCommunicator(ip, port) as TcpServerCommunicator;
if (!communicator) {
console.error(`No communicator found for ${clientId}`);
return;
}
// Handle incoming chunked message via communicator
await communicator.handleIncomingChunk(data);
// Fetch and process result if available
const handlerResult = communicator.getHandlerResult();
if (handlerResult) {
try {
await communicator.sendChunkedMessage(
handlerResult.operationCode,
handlerResult.metaInfo,
handlerResult.fileContent
);
console.log(`Response sent to ${clientId}`);
} catch (err) {
console.error(`Failed to send response to ${clientId}:`, err);
}
}
}
}
+140
View File
@@ -0,0 +1,140 @@
import dgram from 'dgram';
import ping from 'ping';
import { OperationHandler } from '../operations_base/operation_handler';
import { MessageHandler } from '../message_handler';
import { GeneralOperations } from "../operations_custom/general_operations";
import { operationCodes } from "../operation_codes";
import os from 'os';
export class UdpClient {
private udpSocket: dgram.Socket;
private readonly port: number;
private operationHandler: OperationHandler;
constructor(port: number) {
this.port = port;
this.udpSocket = dgram.createSocket('udp4');
this.operationHandler = OperationHandler.getInstance();
this.operationHandler.loadPlugin(new GeneralOperations());
}
// Send heartbeat and get clients that respond with ALIVE, excluding the host machine's IP addresses
async getAliveClients(): Promise<string[]> {
const subnet = this.getSubnet();
const ipRange = this.getIPRange(subnet);
// Get local machine's IP addresses to exclude
const localIPs = this.getLocalIPs();
// First, filter active IPs that respond to ping
const activeIps = await this.filterActiveIps(ipRange);
// Send heartbeat to each active IP and keep only those that respond with ALIVE
const aliveClients: string[] = [];
for (const ip of activeIps) {
if (!localIPs.includes(ip)) {
const result = await this.sendHeartbeat(ip);
if (result.found) {
aliveClients.push(ip);
}
}
}
return aliveClients; // Return the list of IPs that responded with ALIVE, excluding the host machine
}
// Get local IP addresses of the host machine (excluding loopback)
private getLocalIPs(): string[] {
const interfaces = os.networkInterfaces();
const localIPs: string[] = [];
Object.values(interfaces).forEach((iface) => {
iface?.forEach((address) => {
if (address.family === 'IPv4' && !address.internal) {
localIPs.push(address.address);
}
});
});
return localIPs;
}
// Send heartbeat to an IP
private async sendHeartbeat(ip: string): Promise<{ found: boolean }> {
return new Promise((resolve) => {
const heartbeatCode = operationCodes.HEARTBEAT;
const heartbeatMessage = MessageHandler.formatMessage(heartbeatCode);
this.udpSocket.send(heartbeatMessage, this.port, ip, (err) => {
if (err) {
resolve({ found: false });
} else {
const timeout = setTimeout(() => {
this.dropConnection(ip);
resolve({ found: false });
}, 1500);
this.udpSocket.once('message', (msg, rinfo) => {
if (rinfo.address === ip) {
clearTimeout(timeout);
const parsedMessage = MessageHandler.parseMessage(msg.toString());
if (parsedMessage?.operationCode === operationCodes.ALIVE) {
resolve({ found: true });
} else {
resolve({ found: false });
}
}
});
}
});
});
}
// Drop connection for a specific IP
private dropConnection(ip: string): void {
try {
this.udpSocket.removeAllListeners('message');
} catch (err: any) {
console.error(`Error dropping connection to ${ip}: ${err.message}`);
}
}
// Get the subnet (e.g., 192.168.1)
private getSubnet(): string {
const interfaces = os.networkInterfaces();
for (const iface of Object.values(interfaces)) {
for (const address of iface || []) {
if (address.family === 'IPv4' && !address.internal) {
return address.address.split('.').slice(0, 3).join('.');
}
}
}
return '';
}
// Get IP range (assuming /24 subnet)
private getIPRange(subnet: string): string[] {
const ipRange = [];
for (let i = 1; i < 255; i++) {
ipRange.push(`${subnet}.${i}`);
}
return ipRange;
}
// Filter only active IPs by pinging each IP in the range
private async filterActiveIps(ipRange: string[]): Promise<string[]> {
const activeIps: string[] = [];
const pingPromises = ipRange.map(ip => ping.promise.probe(ip, { timeout: 1 }));
const pingResults = await Promise.all(pingPromises);
for (const result of pingResults) {
if (result.alive) {
activeIps.push(result.host);
}
}
return activeIps;
}
}
+64
View File
@@ -0,0 +1,64 @@
import dgram, { RemoteInfo } from 'dgram';
import { UdpSocketCommunicator } from "../socket_communicator/udp_socket_communicator";
import { OperationHandler } from "../operations_base/operation_handler";
import { GeneralOperations} from "../operations_custom/general_operations";
export class UdpServer {
private readonly udpServer: dgram.Socket;
private readonly operationHandler: OperationHandler;
private readonly port: number;
private readonly host: string;
constructor(host: string, port: number) {
this.udpServer = dgram.createSocket('udp4');
this.operationHandler = OperationHandler.getInstance();
this.host = host;
this.port = port;
// Load only the GeneralOperations into the operation handler
this.operationHandler.loadPlugin(new GeneralOperations());
}
// Start the UDP server
public start(): void {
this.udpServer.on('message', this.handleUdpMessages.bind(this));
this.udpServer.on('error', this.handleError);
this.udpServer.on('listening', this.handleListening.bind(this));
// Bind the server to the UDP port and host
this.udpServer.bind(this.port, this.host);
}
// Handle incoming UDP messages
private async handleUdpMessages(msg: Buffer, rinfo: RemoteInfo): Promise<void> {
const ip = rinfo.address;
const port = rinfo.port;
console.log(`Received message from ${ip}:${port}`);
// Create a temporary communicator for the incoming message
const communicator = new UdpSocketCommunicator(this.udpServer, ip, port, this.operationHandler);
// Process the incoming message using the communicator
communicator.handleIncomingMessage(msg.toString());
const communicatorResult = communicator.getHandlerResult();
if (communicatorResult) {
// Send response back to the client using the temporary communicator
await communicator.sendMessage(communicatorResult.operationCode, communicatorResult.metaInfo);
} else {
console.error(`No handler result for ${ip}:${port}`);
}
}
// Handle UDP server errors
private handleError(err: Error): void {
console.error(`UDP server error:\n${err.stack}`);
}
// Handle when the UDP server starts listening
private handleListening(): void {
const address = this.udpServer.address();
console.log(`UDP server listening on ${address.address}:${address.port}`);
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

-71
View File
@@ -1,71 +0,0 @@
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-family: "Roboto", sans-serif;
font-weight: 500;
font-style: normal;
background-image: url('../assets/background.png');
background-size: cover;
background-repeat: no-repeat;
}
.container {
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
min-height: 100vh;
}
h1 {
margin: 0;
padding: 0;
}
.main_component {
display: flex;
flex-wrap: wrap;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #535C91;
opacity: 71;
padding: 2vh;
border-radius: 1rem;
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
width: 90vw;
height: 80vh;
}
.header {
color: #1B1A55;
font-size: 0.8rem;
text-align: center;
text-transform: uppercase;
}
button {
font-weight: bold;
width: 25%;
font-size: 1rem;
padding: 10px;
border: none;
border-radius: 5px;
margin-top: 20px;
cursor: pointer;
}
button:hover {
filter: brightness(85%);
}
button[name="close"] {
background-color: #2196F3;
color: white;
}
@@ -1,99 +0,0 @@
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-family: "Roboto", sans-serif;
font-weight: 500;
font-style: normal;
background-image: url('../assets/background.png');
background-size: cover;
background-repeat: no-repeat;
}
.container {
opacity: 0;
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
min-height: 100vh;
}
.department-form {
background-color: #535C91;
opacity: 71;
padding: 8vh 3vh 5vh;
border-radius: 1rem;
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
width: 25%;
}
.department-form-title {
margin-bottom: 5vh;
color: #FFFFFF;
text-align: center;
}
.department-form-title h2 {
color: #FFFFFF;
margin: 0;
padding: 0;
font-size: 5vh;
}
.department-form-title hr {
width: 65%;
}
.department-form-content {
display: flex;
margin-left: 2rem;
flex-direction: column;
align-items: start;
font-size: 1.5rem;
color: white;
font-weight: bold;
}
.department-form-content input {
margin: 0.7rem;
}
.department-form-footer {
margin-top: 7vh;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: center;
align-items: center;
}
button {
font-weight: bold;
width: 35%;
font-size: 1rem;
padding: 10px;
border: none;
border-radius: 5px;
margin-top: 5px;
cursor: pointer;
}
button:hover {
filter: brightness(85%);
}
button[name="submit"] {
background-color: #F44336;
color: white;
}
button[name="back"] {
background-color: #23BDEE;
color: white;
}
-98
View File
@@ -1,98 +0,0 @@
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-family: "Roboto", sans-serif;
font-weight: 500;
font-style: normal;
background-image: url('../assets/background.png');
background-size: cover;
background-repeat: no-repeat;
}
.container {
opacity: 0;
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
min-height: 100vh;
}
.ip-form {
background-color: #535C91;
opacity: 71;
padding: 9vh 3vh 5vh;
border-radius: 1rem;
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
width: 25%;
}
.ip-form-title {
margin: 0 0 5vh 0;
text-align: center;
}
.ip-form-title h2 {
color: #FFFFFF;
font-size: 5vh;
margin: 0;
padding: 0;
}
.ip-form hr {
width: 40%;
}
.ip-form-content {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
input {
text-align: center;
color: white;
font-weight: bold;
font-size: 1.2rem;
width: 70%;
padding: 1rem;
margin: 0.7rem;
background-color: #1B1A55;
border-radius: 10px;
}
button {
font-weight: bold;
width: 35%;
font-size: 1rem;
padding: 10px;
border: none;
border-radius: 5px;
margin-top: 1rem;
cursor: pointer;
}
button:hover {
filter: brightness(85%);
}
.ip-form-footer {
margin-top: 3vh;
display: flex;
flex-wrap: wrap;
align-content: center;
align-items: center;
justify-content: space-evenly;
}
button[name="submit"] {
background-color: #F44336;
color: white;
}
-110
View File
@@ -1,110 +0,0 @@
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-family: "Roboto", sans-serif;
font-weight: 500;
font-style: normal;
background-image: url('../assets/background.png');
background-size: cover;
background-repeat: no-repeat;
}
.container {
opacity: 0;
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
min-height: 100vh;
}
.header {
color: #1B1A55;
font-size: 4vh;
text-transform: uppercase;
line-height: 2.5rem;
margin-bottom: 10rem;
}
.login-form {
background-color: #535C91;
opacity: 71;
padding: 9vh 3vh 5vh;
border-radius: 1rem;
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
width: 25%;
}
.login-form-title {
margin: 0 0 5vh 0;
text-align: center;
}
.login-form-title h2 {
color: #FFFFFF;
font-size: 5vh;
margin: 0;
padding: 0;
}
.login-form hr {
width: 40%;
}
.login-form-content {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
input {
text-align: center;
color: white;
font-weight: bold;
font-size: 1.2rem;
width: 70%;
padding: 1rem;
margin: 0.7rem;
background-color: #1B1A55;
border-radius: 10px;
}
button {
font-weight: bold;
width: 35%;
font-size: 1rem;
padding: 10px;
border: none;
border-radius: 5px;
margin-top: 1rem;
cursor: pointer;
}
button:hover {
filter: brightness(85%);
}
.login-form-footer {
margin-top: 3vh;
display: flex;
flex-wrap: wrap;
align-content: center;
align-items: center;
justify-content: space-evenly;
}
button[name="submit"] {
background-color: #F44336;
color: white;
}
button[name="signup"] {
background-color: #2196F3;
color: white;
}
-232
View File
@@ -1,232 +0,0 @@
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-family: "Roboto", sans-serif;
font-weight: 500;
font-style: normal;
background-image: url('../assets/background.png');
background-size: cover;
background-repeat: no-repeat;
}
.overlay {
display: none;
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.85);
z-index: 2;
cursor: pointer;
}
.ceo-validation-form {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
padding: 20px;
background: #1B1A55;
border-radius: 10px;
cursor: default;
}
.ceo-validation-form h2 {
text-align: center;
color: white;
}
.form-actions {
text-align: center;
padding-top: 20px;
}
.form-actions button {
padding: 10px 20px;
margin: 0 10px;
border: none;
border-radius: 5px;
cursor: pointer;
}
input {
text-align: center;
color: white;
font-weight: bold;
font-size: 1.2rem;
width: 80%;
padding: 1rem;
margin: 0.7rem;
background-color: #1B1A55;
border-radius: 10px;
}
#back_button {
background-color: #f44336;
width: 35%;
height: auto;
color: white;
}
#submit_button {
background-color: #4CAF50;
width: 35%;
height: auto;
color: white;
}
.container {
opacity: 0;
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
min-height: 100vh;
text-align: center;
}
.left_block {
display: flex;
flex-direction: column;
background-color: #535C91;
opacity: 71;
padding: 2rem;
border-radius: 1rem;
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
color: white;
}
.left_block_top {
display: flex;
flex-direction: row;
justify-content: space-between;
text-align: left;
}
.left_block_top h1 {
padding: 0;
margin: 0;
}
.left_block_top img {
margin: 0 3vw 0 5vw;
width: 8vw;
height: 8vw;
}
.left_block_content {
margin: 2rem 0 2rem 0;
}
.left_block_buttons {
display: flex;
flex-direction: row;
align-content: center;
align-items: center;
justify-content: center;
}
button {
margin: 0 1rem 0 1rem;
width: 15vw;
height: 10vh;
border: none;
border-radius: 10px;
color: white;
font-size: 1rem;
font-weight: bold;
text-transform: uppercase;
cursor: pointer;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
outline: none;
transition: background-color 0.3s ease;
}
button:hover {
filter: brightness(85%);
}
.left_block_footer {
display: flex;
align-items: center;
justify-content: end;
}
.right_block {
display: flex;
flex-direction: column;
background-color: #535C91;
opacity: 71;
padding: 2rem;
border-radius: 1rem;
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
color: white;
}
.notifications {
display: flex;
margin: 1rem 2rem 2rem 3rem;
flex-direction: column;
align-items: start;
height: 40vh; /* Fixed height */
width: 80%; /* Full width */
overflow: auto; /* Enable scrolling */
font-size: 1.2rem;
color: white;
font-weight: bold;
}
.notifications::-webkit-scrollbar {
width: 10px; /* Reduced width of the scrollbar by 2px */
}
.notifications::-webkit-scrollbar-track {
background: #1B1A55; /* Updated track color */
}
.notifications::-webkit-scrollbar-thumb {
background: #535C91; /* Updated handle color */
border: 2px solid #1B1A55; /* Adding a border to effectively reduce the thumb size */
}
.notifications::-webkit-scrollbar-thumb:hover {
background: #555; /* Updated handle color on hover */
}
button[name="logout"] {
margin: 0;
padding: 0;
width: 10vw;
height: 5vh;
background-color: #F44336;
}
button[name="alert"] {
margin: 0.7rem;
background-color: #F44336;
}
button[name="notification"] {
margin: 0.7rem;
background-color: #23BDEE;
}
button[name="menu_button"] {
margin: 0.7rem;
background-color: #1B1A55;
}
-110
View File
@@ -1,110 +0,0 @@
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-family: "Roboto", sans-serif;
font-weight: 500;
font-style: normal;
background-image: url('../assets/background.png');
background-size: cover;
background-repeat: no-repeat;
}
.container {
opacity: 0;
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
min-height: 100vh;
text-align: center; /* Center the text for all child elements */
}
.header {
color: #1B1A55;
font-size: 4vh;
text-transform: uppercase;
line-height: 2.5rem;
margin-bottom: 10rem;
}
.profile-form {
background-color: #535C91;
opacity: 71;
padding: 13vh 3vh 5vh;
border-radius: 1rem;
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
width: 25%;
}
.profile-form-title {
margin-bottom: 5vh;
}
.profile-form hr {
width: 40%;
}
.profile-form-title h2 {
color: #FFFFFF;
margin: 0;
padding: 0;
text-align: center;
font-size: 5vh;
}
.profile-form-content {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.profile-form-footer {
margin-top: 7vh;
display: flex;
flex-direction: row;
justify-content: center;
}
input {
text-align: center;
color: white;
font-weight: bold;
font-size: 1.2rem;
width: 70%;
padding: 1rem;
margin: 0.7rem;
background-color: #1B1A55;
border-radius: 10px;
}
button {
font-weight: bold;
width: 35%;
font-size: 1rem;
padding: 10px;
border: none;
border-radius: 5px;
margin-top: 1rem;
cursor: pointer;
}
button:hover {
filter: brightness(85%);
}
button[name="submit"] {
background-color: #F44336;
color: white;
}
button[name="login"] {
background-color: #2196F3;
color: white;
margin-right: 5rem;
}
@@ -1,39 +0,0 @@
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-family: "Roboto", sans-serif;
font-weight: 500;
font-style: normal;
background-image: url('../assets/background.png');
background-size: cover;
background-repeat: no-repeat;
}
.container {
opacity: 0;
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
min-height: 100vh;
text-align: center; /* Center the text for all child elements */
}
.header {
color: #1B1A55;
font-size: 4vh;
text-transform: uppercase;
line-height: 2.5rem;
margin-bottom: 10rem;
}
img {
width: 20%;
height: 20%;
}
-191
View File
@@ -1,191 +0,0 @@
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-family: "Roboto", sans-serif;
font-weight: 500;
font-style: normal;
background-image: url('../assets/background.png');
background-size: cover;
background-repeat: no-repeat;
}
.container {
opacity: 0;
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
min-height: 100vh;
text-align: center;
}
h1, h2 {
padding: 0;
margin: 0;
}
h2 {
font-size: 1rem;
}
.left_block {
display: flex;
flex-direction: column;
background-color: #535C91;
opacity: 71;
padding: 2rem;
border-radius: 1rem;
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
color: white;
}
.left_block_top {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.left_block_top_left {
display: flex;
flex-direction: column;
justify-content: start;
text-align: left;
}
.left_block_top_left hr {
margin: 0 0 1rem 0;
width: 45%;
}
.left_block_top_right {
width: 10vw;
display: flex;
flex-wrap: wrap;
align-content: center;
justify-content: center;
padding: 0 2vh 0 2vh;
background-color: #1B1A55;
margin: 0 0 0 5vw;
border-radius: 1rem;
}
.left_block_content {
display: flex;
justify-content: start;
align-items: center;
margin: 2rem 0 2rem 0;
}
.left_block_buttons {
display: flex;
flex-direction: row;
align-content: center;
align-items: center;
justify-content: center;
}
button {
margin: 0 1rem 0 1rem;
width: 10vw;
height: 5vh;
border: none;
border-radius: 10px;
color: white;
font-size: 1rem;
font-weight: bold;
text-transform: uppercase;
cursor: pointer;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
outline: none;
transition: background-color 0.3s ease;
}
button:hover {
filter: brightness(85%);
}
.left_block_footer {
display: flex;
align-items: center;
justify-content: end;
}
.right_block {
display: flex;
flex-direction: column;
background-color: #535C91;
opacity: 71;
padding: 2rem 4rem 2rem 2rem;
border-radius: 1rem;
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
color: white;
}
.choose_user_form_title {
display: flex;
flex-wrap: wrap;
flex-direction: column;
justify-content: left;
align-content: start;
}
.choose_user_form_title hr {
width: 80%;
}
.choose_user_form_content {
display: flex;
margin: 1rem 7rem 2rem 0.5rem;
flex-direction: column;
align-items: start;
height: 20vh; /* Fixed height */
width: 100%; /* Full width */
overflow: auto; /* Enable scrolling */
font-size: 1.2rem;
color: white;
font-weight: bold;
}
.choose_user_form_content::-webkit-scrollbar {
width: 10px; /* Reduced width of the scrollbar by 2px */
}
.choose_user_form_content::-webkit-scrollbar-track {
background: #1B1A55; /* Updated track color */
}
.choose_user_form_content::-webkit-scrollbar-thumb {
background: #535C91; /* Updated handle color */
border: 2px solid #1B1A55; /* Adding a border to effectively reduce the thumb size */
}
.choose_user_form_content::-webkit-scrollbar-thumb:hover {
background: #555; /* Updated handle color on hover */
}
button[name="back"] {
background-color: #23BDEE;
}
button[name="submit"] {
background-color: #F44336;
}
button[name="select_file"] {
margin: 0;
width: 8vw;
background-color: #1B1A55;
}
@@ -1,34 +0,0 @@
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-family: "Roboto", sans-serif;
font-weight: 500;
font-style: normal;
background-image: url('../assets/background.png');
background-size: cover;
background-repeat: no-repeat;
}
.container {
opacity: 0;
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
min-height: 100vh;
text-align: center; /* Center the text for all child elements */
}
.header {
color: #1B1A55;
font-size: 4vh;
text-transform: uppercase;
line-height: 2.5rem;
margin-bottom: 10rem;
}
@@ -1,129 +0,0 @@
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-family: "Roboto", sans-serif;
font-weight: 500;
font-style: normal;
background-image: url('../assets/background.png');
background-size: cover;
background-repeat: no-repeat;
}
.container {
opacity: 0;
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
min-height: 100vh;
text-align: center;
}
.header {
color: #1B1A55;
font-size: 4vh;
text-transform: uppercase;
line-height: 2.5rem;
margin-bottom: 10rem;
}
.signup-form {
background-color: #535C91;
opacity: 71;
padding: 8vh 3vh 5vh;
border-radius: 1rem;
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
width: 25%;
}
.signup-form-title {
margin-bottom: 5vh;
}
.signup-form-title h2 {
color: #FFFFFF;
margin: 0;
padding: 0;
text-align: center;
font-size: 5vh;
}
.signup-form hr {
width: 65%;
}
input {
margin: 0 0 1rem 0;
}
.signup-form-content {
display: flex;
margin: 1rem 2rem 2rem 3rem;
flex-direction: column;
align-items: start;
height: 20vh; /* Fixed height */
width: 80%; /* Full width */
overflow: auto; /* Enable scrolling */
font-size: 1.2rem;
color: white;
font-weight: bold;
}
.signup-form-content::-webkit-scrollbar {
width: 10px; /* Reduced width of the scrollbar by 2px */
}
.signup-form-content::-webkit-scrollbar-track {
background: #1B1A55; /* Updated track color */
}
.signup-form-content::-webkit-scrollbar-thumb {
background: #535C91; /* Updated handle color */
border: 2px solid #1B1A55; /* Adding a border to effectively reduce the thumb size */
}
.signup-form-content::-webkit-scrollbar-thumb:hover {
background: #555; /* Updated handle color on hover */
}
.signup-form-footer {
margin-top: 5vh;
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
button {
font-weight: bold;
width: 30%;
font-size: 1rem;
padding: 10px;
border: none;
border-radius: 5px;
margin-top: 5px;
cursor: pointer;
}
button:hover {
filter: brightness(85%);
}
button[name="submit"] {
background-color: #F44336;
color: white;
}
button[name="back"] {
background-color: #23BDEE;
color: white;
}
-105
View File
@@ -1,105 +0,0 @@
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-family: "Roboto", sans-serif;
font-weight: 500;
font-style: normal;
background-image: url('../assets/background.png');
background-size: cover;
background-repeat: no-repeat;
}
.container {
opacity: 0;
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-items: center;
min-height: 100vh;
text-align: center;
}
.header {
color: #1B1A55;
font-size: 4vh;
text-transform: uppercase;
line-height: 2.5rem;
margin-bottom: 10rem;
}
.signup-form {
background-color: #535C91;
opacity: 71;
padding: 13vh 3vh 5vh;
border-radius: 1rem;
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
width: 25%;
}
.signup-form-title {
margin-bottom: 5vh;
}
.signup-form-title h2 {
color: #FFFFFF;
margin: 0;
padding: 0;
text-align: center;
font-size: 5vh;
}
.signup-form hr {
width: 40%;
}
input {
text-align: center;
color: white;
font-weight: bold;
font-size: 1.2rem;
width: 70%;
padding: 1rem;
margin: 0.7rem;
background-color: #1B1A55;
border-radius: 10px;
}
.signup-form-footer {
margin-top: 3vh;
display: flex;
flex-wrap: wrap;
align-content: center;
align-items: center;
justify-content: space-evenly;
}
button {
font-weight: bold;
width: 35%;
font-size: 1rem;
padding: 10px;
border: none;
border-radius: 5px;
margin-top: 5px;
cursor: pointer;
}
button:hover {
filter: brightness(85%);
}
button[name="login"] {
background-color: #2196F3;
color: white;
}
button[name="submit"] {
background-color: #F44336;
color: white;
}
-25
View File
@@ -1,25 +0,0 @@
.fade-in {
animation: fadeInAnimation 0.5s ease-in forwards;
}
.fade-out {
animation: fadeOutAnimation 0.5s ease-out forwards;
}
@keyframes fadeInAnimation {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeOutAnimation {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
-21
View File
@@ -1,21 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="../css/alert_modal.css" rel="stylesheet">
<script src="../js/alert_modal.js"></script>
<title>Alert Modal</title>
</head>
<body>
<div class="container" id="myModal">
<div class="main_component">
<div class="header">
<!--Here goes the message-->
<h1 id="modal-message"></h1>
</div>
<button id="closeButton" name="close">Close</button>
</div>
</div>
</body>
</html>
@@ -1,33 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
<link href="../css/change_department.css" rel="stylesheet">
<link href="../css/transition.css" rel="stylesheet">
<script src="../js/change_department.js"></script>
<script src="../js/transition.js"></script>
<title>Department Selection</title>
</head>
<body onload="fadeIn()">
<div class="container">
<form class="department-form" id="departmentForm">
<div class="department-form-title">
<h2>Choose your department</h2>
<hr>
</div>
<div class="department-form-content">
</div>
<div class="department-form-footer">
<button id="back" name="back" type="button">Back</button>
<button id="submit" name="submit" type="submit">Submit</button>
</div>
</form>
</div>
</body>
</html>
@@ -1,32 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
<link href="../css/sending_file_confirmation.css" rel="stylesheet">
<link href="../css/transition.css" rel="stylesheet">
<script src="../js/transition.js"></script>
<script>
document.addEventListener('DOMContentLoaded', async function () {
const destPath = await window.electronAPI.openDirDialog();
await window.electronAPI.decryptFiles(destPath);
fadeOut('main_menu.html');
});
</script>
<title>Sending file</title>
</head>
<body onload="fadeIn()">
<div class="container">
<div class="header">
<h1>DECRYPTING BACKUP!</h1>
<h2>PlEASE WAIT</h2>
<img alt="Description of GIF" src="../assets/loading.gif">
</div>
</div>
</body>
</html>
-32
View File
@@ -1,32 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
<link href="../../../../CEO/src/renderer/css/ip_submit.css" rel="stylesheet">
<link href="../css/transition.css" rel="stylesheet">
<script src="../js/ip_submit.js"></script>
<script src="../js/transition.js"></script>
<title>IP Submit</title>
</head>
<body onload="fadeIn()">
<div class="container">
<form class="ip-form" id="ipForm">
<div class="ip-form-title">
<h2>IP Config</h2>
<hr>
</div>
<div class="ip-form-content">
<input id="ipInput" name="ip" placeholder="192.168.x.x : Port" type="text">
</div>
<div class="ip-form-footer">
<button id="submit" name="submit" type="submit">Submit</button>
</div>
</form>
</div>
</body>
</html>
-38
View File
@@ -1,38 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
<link href="../css/login.css" rel="stylesheet">
<link href="../css/transition.css" rel="stylesheet">
<script src="../js/login.js"></script>
<script src="../js/transition.js"></script>
<title>Login</title>
</head>
<body onload="fadeIn()">
<div class="container">
<div class="header">
<h1>DO WE KNOW</h1>
<h1>EACH OTHER?</h1>
</div>
<form class="login-form" id="loginForm">
<div class="login-form-title">
<h2>Login</h2>
<hr>
</div>
<div class="login-form-content">
<input name="email" placeholder="Email" type="email">
<input name="password" placeholder="Password" type="password">
</div>
<div class="login-form-footer">
<button id="signup" name="signup" type="submit">Sign Up</button>
<button id="submit" name="submit" type="submit">Submit</button>
</div>
</form>
</div>
</body>
</html>
-68
View File
@@ -1,68 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
<link href="../css/main_menu.css" rel="stylesheet">
<link href="../css/transition.css" rel="stylesheet">
<script src="../js/main_menu.js"></script>
<script src="../js/transition.js"></script>
<title>Main Page</title>
</head>
<body onload="fadeIn()">
<div class="overlay" id="overlay">
<form class="ceo-validation-form" id="ceo_validation">
<h2>CEO Authentication</h2>
<input id="ceo_password" placeholder="Enter CEO's password" required type="password">
<div class="form-actions">
<button id="back_button" type="button">Back</button>
<button id="submit_button" type="submit">Submit</button>
</div>
</form>
</div>
<div class="container">
<div class="left_block">
<div class="left_block_top">
<div>
<h1>WELCOME BACK,</h1>
<h1 id="username_field"></h1>
<h2>Hope you have a productive day!</h2>
</div>
<img alt="" src="../assets/user_1144760.png">
</div>
<div class="left_block_content">
<div class="left_block_buttons">
<button id="change_info" name="menu_button">Change your info</button>
<button id="change_department" name="menu_button">Change work department</button>
</div>
<div class="left_block_buttons">
<button id="backup_dir" name="menu_button">Set backup directory</button>
<button id="department_dir" name="menu_button">Set department directory</button>
<button id="share_dir" name="menu_button">Set share directory</button>
</div>
<div class="left_block_buttons">
<button id="share_file" name="menu_button">Share a file</button>
<button id="decrypt" name="menu_button">Decrypt files</button>
</div>
</div>
<div class="left_block_footer">
<button id="logout" name="logout">Logout</button>
</div>
</div>
<div class="right_block">
<div>
<h1>NOTIFICATIONS</h1>
<hr>
</div>
<div class="notifications" id="notifications">
</div>
</div>
</div>
</body>
</html>
-35
View File
@@ -1,35 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
<link href="../css/profile.css" rel="stylesheet">
<link href="../css/transition.css" rel="stylesheet">
<script src="../js/profile.js"></script>
<script src="../js/transition.js"></script>
<title>Profile</title>
</head>
<body onload="fadeIn()">
<div class="container">
<form class="profile-form" id="profileForm">
<div class="profile-form-title">
<h2>Profile</h2>
<hr>
</div>
<div class="profile-form-content">
<input name="email" placeholder="Email" type="email">
<input name="username" placeholder="Username" type="text">
<input name="password" placeholder="Password" type="password">
</div>
<div class="profile-form-footer">
<button name="login" type="button">Back</button>
<button name="submit" type="submit">Submit</button>
</div>
</form>
</div>
</body>
</html>
@@ -1,26 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
<link href="../css/sending_file_confirmation.css" rel="stylesheet">
<link href="../css/transition.css" rel="stylesheet">
<script src="../js/transition.js"></script>
<script src="../js/sending_file_confirmation.js"></script>;
<title>Sending file</title>
</head>
<body onload="fadeIn()">
<div class="container">
<div class="header">
<h1>SENDING THE FILE!</h1>
<h2>PlEASE WAIT</h2>
<img alt="Description of GIF" src="../assets/loading.gif">
</div>
</div>
</body>
</html>
-49
View File
@@ -1,49 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
<link href="../css/share_file.css" rel="stylesheet">
<link href="../css/transition.css" rel="stylesheet">
<script src="../js/share_file.js"></script>
<script src="../js/transition.js"></script>
<title>Share File</title>
</head>
<body onload="fadeIn()">
<div class="container">
<div class="left_block">
<div class="left_block_top">
<div class="left_block_top_left">
<h1>SHARE A FILE</h1>
<hr>
<h2>First select the file you want to share</h2>
<h2>(can be whatever resource from the system)</h2>
</div>
<div class="left_block_top_right">
<h2 id="fileName"></h2>
</div>
</div>
<div class="left_block_content">
<button id="selectFile" name="select_file" type="button">Select</button>
</div>
<div class="left_block_footer">
<button id="backButton" name="back" type="button">Back</button>
<button id="submitButton" name="submit" type="submit">Submit</button>
</div>
</div>
<form class="right_block" id="userDestForm">
<div class="choose_user_form_title">
<h1>USERS</h1>
<hr>
</div>
<div class="choose_user_form_content">
<!-- Insert the users from the database -->
</div>
</form>
</div>
</body>
</html>
@@ -1,25 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
<link href="../css/sign_up_confirmation.css" rel="stylesheet">
<link href="../css/transition.css" rel="stylesheet">
<script src="../js/sign_up_confirmation.js"></script>
<script src="../js/transition.js"></script>
<title>Setup Completion</title>
</head>
<body onload="fadeIn()">
<div class="container">
<div class="header">
<h1>ALL THE SETUP IS DONE!</h1>
<h2>LETS PROCEED TO THE</h2>
<h2>LOGIN PAGE</h2>
</div>
</div>
</body>
</html>
@@ -1,38 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
<link href="../css/sign_up_department.css" rel="stylesheet">
<link href="../css/transition.css" rel="stylesheet">
<script src="../js/sign_up_departments.js"></script>
<script src="../js/transition.js"></script>
<title>Department Selection</title>
</head>
<body onload="fadeIn()">
<div class="container">
<div class="header">
<h1>TELL ME MORE</h1>
<h1>ABOUT</h1>
<h1>YOUR WORK</h1>
</div>
<form class="signup-form" id="signupForm">
<div class="signup-form-title">
<h2>Choose your department</h2>
<hr>
</div>
<div class="signup-form-content">
<!-- add the list query for departments-->
</div>
<div class="signup-form-footer">
<button id="back" name="back" type="button">Back</button>
<button id="submit" name="submit" type="submit">Submit</button>
</div>
</form>
</div>
</body>
</html>
@@ -1,37 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
<link href="../css/transition.css" rel="stylesheet">
<link href="../css/sing_up_profile.css" rel="stylesheet">
<script src="../js/sign_up_profile.js"></script>
<script src="../js/transition.js"></script>
<title>Signup</title>
</head>
<body onload="fadeIn()">
<div class="container">
<div class="header">
<h1>LET US MEET</h1>
<h1>EACH OTHER</h1>
</div>
<form class="signup-form" id="signupForm">
<div class="signup-form-title">
<h2>Sign Up</h2>
<hr>
</div>
<div>
<input name="email" placeholder="Email" type="email">
<input name="name" placeholder="Username" type="text">
<input name="password" placeholder="Password" type="password">
</div>
<div class="signup-form-footer">
<button id="login" name="login" type="button">Login</button>
<button id="continue" name="submit" type="submit">Continue</button>
</div>
</form>
</div>
-11
View File
@@ -1,11 +0,0 @@
document.addEventListener('DOMContentLoaded', () => {
const closeButton = document.getElementById('closeButton');
closeButton.addEventListener('click', () => {
window.electronAPI.closeAlertWindow();
});
});
function showAlert(message) {
const modalMessage = document.getElementById('modal-message');
modalMessage.textContent = message;
}
-80
View File
@@ -1,80 +0,0 @@
document.addEventListener('DOMContentLoaded', async function () {
let ip = '';
await window.electronAPI.readFile('ipConfig.json')
.then(result => {
const jsonData = JSON.parse(result.content);
ip = jsonData.ip;
})
await fetch(`http://${ip}/users/departments`, {
method: 'GET',
headers: {
'x-api-key': 'uc_api'
}
}).then(async result => {
const res = await result.json();
const data = res['data'];
const formContent = document.querySelector('.department-form-content');
Object.entries(data).forEach(([key, department]) => {
if(department.name !== 'CEO'){
const label = document.createElement('label');
label.innerHTML = `<input type="radio" name="dept" value="${department.name}">${department.name}`;
formContent.appendChild(label);
}
});
}).catch(async error => {
await window.electronAPI.showAlert(error.message)
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error changing content:', error));
});
document.getElementById('back').addEventListener('click', async function () {
try {
await fadeOut('main_menu.html');
console.log('Content changed successfully');
} catch (error) {
console.error('Error changing content:', error);
}
});
document.getElementById('submit').addEventListener('click', async function (e) {
e.preventDefault();
const selectedDept = document.querySelector('input[name="dept"]:checked').value;
if (!selectedDept) {
throw new Error('No department had been selected!.');
}
let result = await window.electronAPI.readFile('loginData.json');
if (!result.success) {
throw new Error('Error reading the file. Please try again later.');
}
const data = JSON.parse(await result.content);
const id = data.id;
await fetch(`http://${ip}/users/change_department`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'uc_api'
},
body: JSON.stringify({
id: id,
department: selectedDept,
})
}).then(async response => {
if (!response.ok) {
return;
}
fadeOut('main_menu.html')
}).catch(async error => {
console.error(error);
await window.electronAPI.showAlert(error.message)
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error changing content:', error));
});
});
})
-34
View File
@@ -1,34 +0,0 @@
document.addEventListener('DOMContentLoaded', async function () {
const submitButton = document.getElementById('submit');
const ipInput = document.getElementById('ipInput');
submitButton.addEventListener('click', async function (e) {
e.preventDefault();
try {
const ipAddress = ipInput.value.trim();
if (!ipAddress) {
throw new Error('Please enter an IP address.');
}
fetch(`http://${ipAddress}/heartbeat`)
.then(async response => {
if (!response.ok) {
throw new Error('Test failed. Check ip and server.');
}
await window.electronAPI.writeFile('ipConfig.json', JSON.stringify({ip: ipAddress}));
fadeOut('login.html');
})
.catch(async error => {
await window.electronAPI.showAlert('Can\'t reach server.')
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error showing alert:', error));
});
} catch (error) {
console.error('Error:', error.message);
await window.electronAPI.showAlert(error.message)
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error showing alert:', error));
}
});
});
-98
View File
@@ -1,98 +0,0 @@
document.addEventListener('DOMContentLoaded', async function () {
const signupButton = document.getElementById('signup');
const submitButton = document.getElementById('submit');
const signupDataExists = await window.electronAPI.checkFileExists('signupData.json');
if (signupDataExists) {
await window.electronAPI.deleteFile('signupData.json');
}
let ip = '';
await window.electronAPI.readFile('ipConfig.json')
.then(result => {
const jsonData = JSON.parse(result.content);
ip = jsonData.ip;
})
const fileExists = await window.electronAPI.checkFileExists('loginData.json');
if (fileExists) {
await window.electronAPI.readFile('loginData.json')
.then(async result => {
const loginData = JSON.parse(result.content);
console.log(loginData);
const {email, password} = loginData;
await fetch(`http://${ip}/users/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'uc_api'
},
body: JSON.stringify({
email: email,
password: password
})
}).then(async response => {
if (response.ok) {
await window.electronAPI.killBeforeLogout();
await window.electronAPI.startMainProcesses();
fadeOut('main_menu.html');
} else {
await window.electronAPI.deleteFile('loginData.json');
}
}).catch(error => {
console.error(error);
});
})
.catch(async error => {
console.error('Can\'t read loginData');
await window.electronAPI.deleteFile('loginData.json');
});
}
signupButton.addEventListener('click', function (e) {
e.preventDefault();
console.log('Sign up button clicked.')
fadeOut('sign_up_profile.html');
});
submitButton.addEventListener('click', async function (e) {
e.preventDefault();
console.log('Submit button clicked');
const form = document.getElementById('loginForm');
const formData = new FormData(form);
const email = formData.get('email');
const password = formData.get('password');
await fetch(`http://${ip}/users/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'uc_api'
},
body: JSON.stringify({
email: email,
password: password
})
}).then(async response => {
if (!response.ok) {
const data = await response.json();
throw new Error(data.message);
}
return response.json();
}).then(async data => {
data.data.password = password
await window.electronAPI.writeFile('loginData.json', JSON.stringify(data.data, null, 2));
await window.electronAPI.startMainProcesses();
fadeOut('main_menu.html');
})
.catch(async error => {
await window.electronAPI.showAlert(error.message)
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error showing alert:', error));
})
});
});
-289
View File
@@ -1,289 +0,0 @@
document.addEventListener('DOMContentLoaded', async function () {
await insertUsername();
setInterval(loadReceivedFiles, 3000); // Call loadReceivedFiles every 5000 ms (5 seconds)
const backupButton = document.getElementById('backup_dir');
const shareButton = document.getElementById('share_dir');
const departmentButton = document.getElementById('department_dir');
const changeDepartmentButton = document.getElementById('change_department');
const changeInfoButton = document.getElementById('change_info');
const shareFileButton = document.getElementById('share_file');
const decryptButton = document.getElementById('decrypt');
const logoutButton = document.getElementById('logout');
const overlay = document.getElementById('overlay');
const ceoBackButton = document.getElementById('back_button');
const ceoSubmitButton = document.getElementById('submit_button');
let ip = '';
await window.electronAPI.readFile('ipConfig.json')
.then(result => {
const jsonData = JSON.parse(result.content);
ip = jsonData.ip;
})
let triggerSource = '';
async function loadReceivedFiles() {
const checkFileReceived = await window.electronAPI.checkFileExists('filesReceived.json');
if (!checkFileReceived) {
return;
}
try {
const fileData = await window.electronAPI.readFile('filesReceived.json');
const filesJson = JSON.parse(fileData.content);
const receivedFiles = filesJson.receivedFiles;
const notificationsDiv = document.getElementById('notifications');
const existingButtons = Array.from(notificationsDiv.children).map(button => button.getAttribute('data-filepath'));
receivedFiles.forEach(filePath => {
// Check if the button for this file path already exists
if (!existingButtons.includes(filePath)) {
const button = document.createElement('button');
button.setAttribute('data-filepath', filePath); // Set a custom attribute to track the file path
button.name = 'notification';
button.textContent = 'You received a file';
button.addEventListener('click', () => handleFileReceivedButtonPressed(filePath, button));
notificationsDiv.appendChild(button);
}
});
} catch (error) {
console.error('Error loading received files:', error);
}
}
async function handleFileReceivedButtonPressed(filePath, button) {
console.log('Notification button clicked!');
// Open file in file explorer
await window.electronAPI.showFileInExplorer(filePath)
.then(() => console.log('File explorer opened'))
.catch(error => console.error('Error opening file explorer:', error));
// Remove the button
button.remove();
// Call to remove the path from the JSON file
await window.electronAPI.removePathFromReceivedFiles(filePath)
.then(() => console.log('Path removed from received files'))
.catch(error => console.error('Error removing path:', error));
}
function handleOverlayOpen(buttonId) {
overlay.style.display = 'block';
triggerSource = buttonId; // Remember the button that triggered the overlay
console.log(`${buttonId} button clicked!`);
}
ceoBackButton.addEventListener('click', function () {
overlay.style.display = 'none';
});
ceoSubmitButton.addEventListener('click', async function (event) {
event.preventDefault();
const password = document.getElementById('ceo_password').value;
await fetch(`http://${ip}/users/validate_ceo_password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'uc_api',
},
body: JSON.stringify({
password: password
})
}).then(async result => {
const data = await result.json();
if (!result.ok) {
throw new Error(data.message);
}
if (triggerSource === 'change_department') {
fadeOut('change_department.html');
} else if (triggerSource === 'decrypt') {
console.log('astept decryptarea')
fadeOut('decrypting_backup.html');
}
})
overlay.style.display = 'none';
triggerSource = '';
});
checkDirBackupFileExists()
.then(() => console.log('verificare backupDir facuta'));
checkShareDirFileExists()
.then(() => console.log('verificare ShareDir facuta'));
checkDepartmentDirFileExists()
.then(() => {console.log('verificare departmentDir facuta')})
backupButton.addEventListener('click', function () {
console.log('Set backup directory button clicked!');
window.electronAPI.openJsonDirConfigDialog('dirBackup.json')
.then(() => console.log('Back-up directory set'))
.catch(async error => await window.electronAPI.showAlert(error.message)
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error showing alert:', error)));
});
shareButton.addEventListener('click', function () {
console.log('Set backup directory button clicked!');
window.electronAPI.openJsonDirConfigDialog('dirShare.json')
.then(() => console.log('Share directory set'))
.catch(async error => await window.electronAPI.showAlert(error.message)
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error showing alert:', error)));
});
departmentButton.addEventListener('click', function () {
console.log('Set backup directory button clicked!');
window.electronAPI.openJsonDirConfigDialog('dirDepartment.json')
.then(() => console.log('Department directory set'))
.catch(async error => await window.electronAPI.showAlert(error.message)
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error showing alert:', error)));
});
changeInfoButton.addEventListener('click', function () {
console.log('Change your info button clicked!');
fadeOut('profile.html');
});
changeDepartmentButton.addEventListener('click', function () {
handleOverlayOpen('change_department');
});
decryptButton.addEventListener('click', function () {
handleOverlayOpen('decrypt');
});
shareFileButton.addEventListener('click', function () {
console.log('Share a file button clicked!');
fadeOut('share_file.html');
});
logoutButton.addEventListener('click', async function () {
console.log('Logout button clicked!');
await window.electronAPI.killBeforeLogout();
await window.electronAPI.deleteFile('loginData.json');
fadeOut('login.html');
});
async function checkDirBackupFileExists() {
try {
// Make an IPC call to check file existence
const fileExists = await window.electronAPI.checkFileExists('dirBackup.json');
if (!fileExists) {
const notificationsDiv = document.getElementById('notifications');
const button = document.createElement('button');
button.id = 'backup_alert';
button.name = 'alert';
button.textContent = 'Set your backup directory!';
button.addEventListener('click', handleBackupButtonPressed);
notificationsDiv.appendChild(button);
}
} catch (error) {
console.error('Error checking file existence:', error);
}
}
async function checkShareDirFileExists() {
try {
const fileExists = await window.electronAPI.checkFileExists('dirShare.json');
if (!fileExists) {
const notificationsDiv = document.getElementById('notifications');
const button = document.createElement('button');
button.id = 'share_dir_alert';
button.name = 'alert';
button.textContent = 'Set your share directory!';
button.addEventListener('click', handleShareButtonPressed);
notificationsDiv.appendChild(button);
}
} catch (error) {
console.error('Error checking file existence:', error);
}
}
async function checkDepartmentDirFileExists() {
try {
const fileExists = await window.electronAPI.checkFileExists('dirDepartment.json');
if (!fileExists) {
const notificationsDiv = document.getElementById('notifications');
const button = document.createElement('button');
button.id = 'department_dir_alert';
button.name = 'alert';
button.textContent = 'Set your department directory!';
button.addEventListener('click', handleDepartmentButtonPressed);
notificationsDiv.appendChild(button);
}
} catch (error) {
console.error('Error checking file existence:', error);
}
}
async function handleBackupButtonPressed() {
console.log('Button clicked!');
await window.electronAPI.openJsonDirConfigDialog('dirBackup.json')
.then(() => console.log('Back-up directory set'))
.catch(async error => await window.electronAPI.showAlert(error.message)
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error showing alert:', error)));
const button = document.getElementById('backup_alert');
button.remove();
}
async function handleShareButtonPressed() {
console.log('Button clicked!');
await window.electronAPI.openJsonDirConfigDialog('dirShare.json')
.then(() => console.log('Share directory set'))
.catch(async error => await window.electronAPI.showAlert(error.message)
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error showing alert:', error)));
const button = document.getElementById('share_dir_alert');
button.remove();
}
async function handleDepartmentButtonPressed() {
console.log('Button clicked!');
await window.electronAPI.openJsonDirConfigDialog('dirDepartment.json')
.then(() => console.log('Department directory set'))
.catch(async error => await window.electronAPI.showAlert(error.message)
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error showing alert:', error)));
const button = document.getElementById('department_dir_alert');
button.remove();
}
async function insertUsername() {
try {
const userData = await window.electronAPI.readFile('loginData.json');
const userJson = JSON.parse(userData.content);
const username = userJson.name;
const usernameField = document.getElementById('username_field');
if (usernameField) {
usernameField.textContent = username + '!';
}
} catch (error) {
console.error('Error loading username:', error);
const usernameField = document.getElementById('username_field');
usernameField.textContent = 'User!';
}
}
});
-77
View File
@@ -1,77 +0,0 @@
document.addEventListener('DOMContentLoaded', async function () {
let ip = '';
await window.electronAPI.readFile('ipConfig.json')
.then(result => {
const jsonData = JSON.parse(result.content);
ip = jsonData.ip;
})
const result = await window.electronAPI.readFile('loginData.json');
const loginData = JSON.parse(result.content);
const emailInput = document.querySelector('input[name="email"]');
const usernameInput = document.querySelector('input[name="username"]');
const passwordInput = document.querySelector('input[name="password"]');
emailInput.value = loginData.email;
usernameInput.value = loginData.name;
passwordInput.value = loginData.password;
const backButton = document.querySelector('button[name="login"]');
const submitButton = document.querySelector('button[name="submit"]');
backButton.addEventListener('click', function () {
console.log('Back button clicked!');
fadeOut('main_menu.html')
});
submitButton.addEventListener('click', async function (e) {
e.preventDefault();
console.log('Submit button clicked!');
const emailInput = document.querySelector('input[name="email"]');
const usernameInput = document.querySelector('input[name="username"]');
const passwordInput = document.querySelector('input[name="password"]');
const result = await window.electronAPI.readFile('loginData.json');
const loginData = JSON.parse(result.content);
const {id, department} = loginData;
const email = emailInput.value;
const name = usernameInput.value;
const password = passwordInput.value;
await fetch(`http://${ip}/users`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'uc_api'
},
body: JSON.stringify({
name: name,
email: email,
password: password
})
}).then(async result => {
const data = await result.json();
if (!result.ok) {
throw new Error(data.message);
}
await window.electronAPI.writeFile('loginData.json', JSON.stringify({
id: id,
name: name,
email: email,
password: password,
department: department
}));
fadeOut('main_menu.html');
}).catch(async error => {
await window.electronAPI.showAlert(error.message)
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error showing alert:', error));
})
});
});
@@ -1,65 +0,0 @@
document.addEventListener("DOMContentLoaded", async function () {
async function performUploads() {
try {
const uploadFile = await window.electronAPI.readFile('usersDestTemp.json');
const uploadData = JSON.parse(uploadFile.content);
const uploadPromises = uploadData.users.map(async (user) => {
try {
const fileResponse = await fetch(uploadData.filePath);
if (!fileResponse.ok) {
throw new Error(`HTTP error when trying to fetch the file: status ${fileResponse.statusText}`);
}
const fileBlob = await fileResponse.blob();
await timeout(5000); // Timeout to simulate delay or wait
const url = `http://${user.destIp}:${user.destPort}/share_file`;
const uploadResponse = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/octet-stream',
'X-IdUser': user.userId,
'X-NameOfFile': user.fileName,
'X-SizeOfFile': fileBlob.size
},
body: fileBlob
});
if (!uploadResponse.ok) {
const response = await uploadResponse.json();
throw new Error(`HTTP error during file upload to ${user.userId}: status ${response.message}`);
}
return {userId: user.userId, success: true, message: `Upload successful for user ${user.userId}`};
} catch (error) {
console.error(`Failed to upload for user ${user.userId}:`, error);
return {userId: user.userId, success: false, message: `Upload failed for user ${user.userId}`};
}
});
const results = await Promise.all(uploadPromises);
results.forEach(result => {
if (result.success) {
console.log(result.message);
} else {
console.error(result.message);
}
});
console.log('All files processed. Check the console for detailed results.');
} catch (error) {
console.error('An error occurred during uploads:', error);
} finally {
await window.electronAPI.deleteFile('usersDestTemp.json');
}
}
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
await performUploads();
await timeout(3000);
fadeOut('main_menu.html'); // Make sure this function is properly defined or available in your context
});
-139
View File
@@ -1,139 +0,0 @@
document.addEventListener("DOMContentLoaded", async function () {
let pathToFile = '';
let serverIp = '';
await window.electronAPI.readFile('ipConfig.json')
.then(result => {
const jsonData = JSON.parse(result.content);
serverIp = jsonData.ip;
})
function updateFileName() {
const fileNameElement = document.getElementById('fileName');
if (pathToFile.trim() === '') {
fileNameElement.textContent = 'No file chosen';
} else {
fileNameElement.textContent = pathToFile.split('\\').pop().split('/').pop();
}
}
updateFileName();
async function fetchUsersAndCreateCheckboxes() {
const result = await window.electronAPI.readFile('loginData.json');
const loginData = JSON.parse(result.content);
const {id} = loginData;
fetch(`http://${serverIp}/users`, {
method: 'GET',
headers: {
'x-api-key': 'uc_api'
}
})
.then(response => response.json())
.then(data => {
const usersDiv = document.querySelector('.choose_user_form_content');
usersDiv.innerHTML = '';
data['data'].forEach(user => {
if (user.id !== id) {
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.name = 'users';
checkbox.value = user.id;
const label = document.createElement('label');
label.innerHTML = `${user.name}`;
label.insertBefore(checkbox, label.firstChild); // Insert checkbox before the label's first child
usersDiv.appendChild(label);
}
});
})
.catch(error => console.error('Error fetching users:', error));
}
document.getElementById('selectFile').addEventListener('click', async function () {
console.log('Select file button clicked');
try {
pathToFile = await window.electronAPI.openFileDialog();
updateFileName();
} catch (error) {
console.error('Error opening file dialog:', error);
}
});
document.getElementById('backButton').addEventListener('click', async function () {
console.log('Back button clicked');
fadeOut('main_menu.html');
});
document.getElementById('submitButton').addEventListener('click', async function (event) {
event.preventDefault();
console.log('Submit button clicked');
// Check if pathToFile has content
if (!pathToFile.trim()) {
await window.electronAPI.showAlert('File not chosen!')
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error showing alert:', error));
return;
}
const form = document.getElementById('userDestForm');
const checkboxes = form.querySelectorAll('input[name="users"]');
const selectedUserIds = Array.from(checkboxes)
.filter(checkbox => checkbox.checked)
.map(checkbox => checkbox.value);
if (!selectedUserIds.length) {
await window.electronAPI.showAlert('No user selected!')
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error showing alert:', error));
return;
}
const uploadData = {
filePath: pathToFile, // Use the pathToFile variable
users: []
};
for (const userId of selectedUserIds) {
try {
const ipResponse = await fetch(`http://${serverIp}/backup_schemes/${userId}`,{
method: 'GET',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'uc_api',
}
});
if (!ipResponse.ok) {
throw new Error(`Failed to fetch IP address for user ${userId}: status ${ipResponse.status}`);
}
const { data: destIp } = await ipResponse.json();
uploadData.users.push({
userId,
destIp,
destPort: 3000, // Static destination port
fileName: pathToFile.split('\\').pop().split('/').pop()
});
} catch (error) {
console.error('Error fetching user data:', error);
}
}
window.electronAPI.writeFile('usersDestTemp.json', JSON.stringify(uploadData, null, 2))
.then(async () => {
console.log('File saved successfully');
fadeOut('sending_file_confirmation.html');
})
.catch(error => console.error('Failed to save file:', error));
});
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
fetchUsersAndCreateCheckboxes().then(() => console.log('Page rendered'))
});
@@ -1,4 +0,0 @@
document.addEventListener('DOMContentLoaded', async function () {
await new Promise(resolve => setTimeout(resolve, 2000));
fadeOut('login.html');
});
@@ -1,90 +0,0 @@
document.addEventListener('DOMContentLoaded', async function () {
let ip = '';
await window.electronAPI.readFile('ipConfig.json')
.then(result => {
const jsonData = JSON.parse(result.content);
ip = jsonData.ip;
})
await fetch(`http://${ip}/users/departments`, {
method: 'GET',
headers: {
'x-api-key': 'uc_api'
}
}).then(async result => {
const res = await result.json();
const data = res['data'];
const formContent = document.querySelector('.signup-form-content');
Object.entries(data).forEach(([key, department]) => {
if(department.name !== 'CEO') {
const label = document.createElement('label');
label.innerHTML = `<input type="radio" name="dept" value="${department.name}">${department.name}`;
formContent.appendChild(label);
}
});
}).catch(async error => {
await window.electronAPI.showAlert(error.message)
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error changing content:', error));
});
document.getElementById('back').addEventListener('click', async function () {
fadeOut('sign_up_profile.html');
});
// Handler for the continue button
document.getElementById('submit').addEventListener('click', async function (e) {
e.preventDefault();
await window.electronAPI.readFile('signupData.json')
.then(async result => {
const selectedDept = document.querySelector('input[name="dept"]:checked').value;
if (!selectedDept) {
throw new Error('No department had been selected!.');
}
const data = JSON.parse(result.content);
const email = data.email;
const name = data.name;
const password = data.password;
const userData = {
name: name,
email: email,
password: password,
department: selectedDept,
}
await fetch(`http://${ip}/users/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'uc_api'
},
body: JSON.stringify(userData)
})
.then(async response => {
let data = await response.json();
if (!response.ok) {
console.log(data);
throw new Error(data.message);
}
data = data.data;
userData['id'] = data.id;
await window.electronAPI.writeFile('loginData.json', JSON.stringify(userData));
fadeOut('sign_up_confirmation.html');
})
})
.catch(async error => {
await window.electronAPI.showAlert(error.message)
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error changing content:', error));
})
});
})
-71
View File
@@ -1,71 +0,0 @@
document.addEventListener('DOMContentLoaded', async function () {
const cancelButton = document.getElementById('login');
const continueButton = document.getElementById('continue');
const signupDataExists = await window.electronAPI.checkFileExists('signupData.json');
if (signupDataExists) {
await window.electronAPI.readFile('signupData.json')
.then(result => {
const signupData = JSON.parse(result.content);
// Assign the values to the form inputs
if (signupData.email) document.querySelector('input[name="email"]').value = signupData.email;
if (signupData.name) document.querySelector('input[name="name"]').value = signupData.name;
if (signupData.password) document.querySelector('input[name="password"]').value = signupData.password;
})
.catch(error => {
console.error('Error reading signup data:', error.message);
});
}
let ip = '';
await window.electronAPI.readFile('ipConfig.json')
.then(result => {
const jsonData = JSON.parse(result.content);
ip = jsonData.ip;
})
cancelButton.addEventListener('click', function () {
console.log(`'Login' button clicked!`);
fadeOut('login.html');
});
continueButton.addEventListener('click', async function (e) {
e.preventDefault();
console.log('Continue button clicked');
const form = document.getElementById('signupForm');
const formData = new FormData(form);
const email = formData.get('email');
await fetch(`http://${ip}/users/validate_email`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'uc_api'
},
body: JSON.stringify({
email: email
})
}).then(async response => {
if (!response.ok) {
const data = await response.json();
throw new Error(data.message);
}
}).then(async () => {
const formDataJSON = {};
formData.forEach((value, key) => {
formDataJSON[key] = value;
});
await window.electronAPI.writeFile('signupData.json', JSON.stringify(formDataJSON));
fadeOut('sign_up_departments.html');
}).catch(async error => {
await window.electronAPI.showAlert(error.message)
.then(() => console.log('Alert window opened'))
.catch(error => console.error('Error changing content:', error));
})
});
});
-16
View File
@@ -1,16 +0,0 @@
function fadeIn() {
document.querySelector('.container').classList.remove('fade-out');
document.querySelector('.container').classList.add('fade-in');
}
function fadeOut(destination) {
const container = document.querySelector('.container');
container.classList.remove('fade-in');
container.classList.add('fade-out');
container.addEventListener('animationend', async () => {
await window.electronAPI.changeContent(destination)
.then(() => console.log('Navigated to dashboard'))
.catch(error => console.error('Error navigating:', error));
});
}
@@ -0,0 +1,38 @@
import { workerData, parentPort } from 'worker_threads';
import { BackupRetrievalWorker} from '../helpers/backup_retrieval'; // Assuming the class is in the same folder
// Destructure the data passed from the WorkerManager
const {
userConfigPath,
applicationInfoPath,
clientPort,
destinationPath
}: {
userConfigPath: string,
applicationInfoPath: string,
clientPort: number,
destinationPath: string
} = workerData;
// Initialize the BackupRetrievalWorker
const backupRetrievalWorker = new BackupRetrievalWorker(
userConfigPath,
applicationInfoPath,
clientPort,
destinationPath
);
// Start the backup retrieval process
backupRetrievalWorker.start()
.then(() => {
parentPort?.postMessage({
success: true,
message: 'Backup retrieval completed successfully.'
});
})
.catch((error: any) => {
parentPort?.postMessage({
success: false,
message: `Backup retrieval failed: ${error.message}`
});
});
@@ -0,0 +1,52 @@
import { workerData } from 'worker_threads';
import { UsersInfoFetcher } from '../helpers/users_info_fetcher';
import {BackupManager} from "../helpers/backup_manager";
import {FileSharer} from "../helpers/file_sharer";
import {DepartmentSharer} from "../helpers/department_sharer";
// Destructure the required information from workerData
const { usersConfigPath, applicationInfoPath, memoryManagerPath, queueManagerPath, tcpPort } = workerData;
const usersInfoFetcher = new UsersInfoFetcher(applicationInfoPath, memoryManagerPath, tcpPort);
usersInfoFetcher.start()
.then(() => {
console.log('Users Info Fetcher started successfully');
})
.catch((error: any) => {
console.error('Error starting Users Info Fetcher:', error);
});
const backupManager = new BackupManager(
usersConfigPath,
applicationInfoPath,
memoryManagerPath,
tcpPort
);
backupManager.start()
.then(() => {
console.log('Backup Manager started successfully');
})
.catch((error: any) => {
console.error('Error starting Backup Manager:', error);
});
const fileSharer = new FileSharer(queueManagerPath, tcpPort);
fileSharer.start()
.then(() => {
console.log('File Sharer started successfully');
})
.catch((error: any) => {
console.error('Error starting File Sharer:', error);
});
const departmentSharer = new DepartmentSharer(usersConfigPath, applicationInfoPath, memoryManagerPath, tcpPort);
departmentSharer.start()
.then(() => {
console.log('Department Sharer started successfully');
})
.catch((error: any) => {
console.error('Error starting Department Sharer:', error);
});
+14
View File
@@ -0,0 +1,14 @@
import {UdpServer} from "../network/udp/udp_server";
import {TcpServer} from "../network/tcp/tcp_server";
import {workerData} from "worker_threads";
let udpServer: UdpServer | null = null;
let tcpServer: TcpServer | null = null;
const {USER_UDP_PORT, USER_TCP_PORT, HOST} = workerData;
udpServer = new UdpServer(HOST, USER_UDP_PORT);
udpServer.start();
tcpServer = new TcpServer(HOST, USER_TCP_PORT);
tcpServer.start();
+43
View File
@@ -0,0 +1,43 @@
import {DirectoryWatcher} from "../helpers/directory_watcher";
import {workerData} from "worker_threads";
const {
memoryManagerPath,
applicationInfoPath,
} = workerData;
const backupDirectoryManager = new DirectoryWatcher(memoryManagerPath, applicationInfoPath, 'backupDirectory');
const departmentShareManager = new DirectoryWatcher(memoryManagerPath, applicationInfoPath, 'departmentDirectory');
const shareFileManager = new DirectoryWatcher(memoryManagerPath, applicationInfoPath, 'shareDirectory');
// Backup directory manager check loop
const backupIntervalId = setInterval(async () => {
if (await backupDirectoryManager.initialize()) {
clearInterval(backupIntervalId); // Stop the loop once initialized
console.log('BackupDirectoryManager successfully initialized.');
} else {
console.log('Retrying BackupDirectoryManager initialization...');
}
}, 60000); // Check every 60 seconds
// Department share manager check loop
const departmentIntervalId = setInterval(async () => {
if (await departmentShareManager.initialize()) {
clearInterval(departmentIntervalId); // Stop the loop once initialized
console.log('DepartmentShareManager successfully initialized.');
} else {
console.log('Retrying DepartmentShareManager initialization...');
}
}, 60000); // Check every 60 seconds
// Department share manager check loop
const shareIntervalId = setInterval(async () => {
if (await shareFileManager.initialize()) {
clearInterval(shareIntervalId); // Stop the loop once initialized
console.log('DepartmentShareManager successfully initialized.');
} else {
console.log('Retrying DepartmentShareManager initialization...');
}
}, 60000); // Check every 60 seconds