BACKEND DONE FOR ALL APPS
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user