UNPKG

@ufdevsllc/authme2.0

Version:

SDK for license management and remote monitoring with automatic system tracking, license validation, and remote control capabilities

583 lines (510 loc) 20.4 kB
import DatabaseManager from './database-manager.js'; class RemoteControlHandler { constructor(errorHandler = null) { this.errorHandler = errorHandler; this.databaseManager = new DatabaseManager(this.errorHandler); this.isPolling = false; this.pollingInterval = null; this.pollingIntervalMs = 30000; // Poll every 30 seconds this.currentLicenseKey = null; this.applicationBlocked = false; this.commandHandlers = new Map(); // Initialize default command handlers this.initializeCommandHandlers(); } /** * Initialize the remote control handler with license key * @param {string} licenseKey - The license key to monitor */ async initialize(licenseKey) { // Validate license key if (this.errorHandler) { const validation = this.errorHandler.validateInput(licenseKey, { type: 'string', required: true, minLength: 8 }, { field: 'licenseKey' }); if (!validation.isValid) { const error = new Error(`Invalid license key for remote control: ${validation.errors.join(', ')}`); await this.errorHandler.handleError(error, { component: 'remote-control-handler', operation: 'initialization', validationErrors: validation.errors }); throw error; } } else { // Basic validation without error handler if (!licenseKey || typeof licenseKey !== 'string' || licenseKey.length < 8) { throw new Error('License key is required for remote control initialization'); } } const context = { component: 'remote-control-handler', operation: 'initialization', licenseKey }; try { if (this.errorHandler) { await this.errorHandler.initialize(); } this.currentLicenseKey = licenseKey; // Initialize database connection await this.databaseManager.initMonitoringConnection(); if (this.errorHandler) { await this.errorHandler.logInfo('Remote control handler initialized successfully', context); } else { console.log(`Remote control handler initialized for license: ${licenseKey}`); } } catch (error) { if (this.errorHandler) { await this.errorHandler.handleError(error, context); } else { console.error('Failed to initialize remote control handler:', error.message); } throw error; } } /** * Initialize default command handlers */ initializeCommandHandlers() { this.commandHandlers.set('block', this.handleBlockCommand.bind(this)); this.commandHandlers.set('activate', this.handleActivateCommand.bind(this)); this.commandHandlers.set('deactivate', this.handleDeactivateCommand.bind(this)); this.commandHandlers.set('update', this.handleUpdateCommand.bind(this)); this.commandHandlers.set('restart', this.handleRestartCommand.bind(this)); this.commandHandlers.set('status', this.handleStatusCommand.bind(this)); this.commandHandlers.set('config', this.handleConfigCommand.bind(this)); } /** * Check for pending commands in the database * @returns {Promise<Array>} Array of pending commands */ async checkForCommands() { if (!this.currentLicenseKey) { throw new Error('Remote control handler not initialized with license key'); } try { const db = this.databaseManager.getMonitoringDB(); const collection = db.collection('remoteCommands'); // Find pending commands for this license key, ordered by priority and creation date const commands = await collection.find({ licenseKey: this.currentLicenseKey, status: 'pending', expiresAt: { $gt: new Date() } }).sort({ priority: -1, createdAt: 1 }).toArray(); return commands; } catch (error) { console.error('Error checking for remote commands:', error); throw error; } } /** * Execute a remote command with validation * @param {Object} command - The command object to execute * @returns {Promise<Object>} Execution result */ async executeCommand(command) { // Validate command object if (this.errorHandler) { const commandValidation = this.errorHandler.validateInput(command, { type: 'object', required: true }, { field: 'command' }); if (!commandValidation.isValid) { const error = new Error(`Invalid command object: ${commandValidation.errors.join(', ')}`); await this.errorHandler.handleError(error, { component: 'remote-control-handler', operation: 'execute-command', validationErrors: commandValidation.errors }); throw error; } } else { // Basic validation without error handler if (!command || typeof command !== 'object') { throw new Error('Invalid command object'); } } const { _id, command: commandType, parameters = {}, licenseKey } = command; // Validate required fields if (!_id || !commandType || !licenseKey) { const error = new Error('Command missing required fields: _id, command, or licenseKey'); if (this.errorHandler) { await this.errorHandler.handleError(error, { component: 'remote-control-handler', operation: 'execute-command', command: command }); } throw error; } if (licenseKey !== this.currentLicenseKey) { const error = new Error('Command license key does not match current license'); if (this.errorHandler) { await this.errorHandler.handleError(error, { component: 'remote-control-handler', operation: 'execute-command', commandLicenseKey: licenseKey, currentLicenseKey: this.currentLicenseKey }); } throw error; } const startTime = Date.now(); const context = { component: 'remote-control-handler', operation: 'execute-command', commandType, commandId: _id, licenseKey }; let result = { success: false, message: '', data: null }; try { // Mark command as executing await this.updateCommandStatus(_id, 'executing'); // Validate command type if (!this.commandHandlers.has(commandType)) { throw new Error(`Unknown command type: ${commandType}`); } // Execute the command with retry logic let handlerResult; if (this.errorHandler) { handlerResult = await this.errorHandler.executeWithRetry( async () => { const handler = this.commandHandlers.get(commandType); return await handler(parameters); }, context, { maxRetries: 2 } ); } else { const handler = this.commandHandlers.get(commandType); handlerResult = await handler(parameters); } result = { success: true, message: handlerResult.message || `Command ${commandType} executed successfully`, data: handlerResult.data || null, executionTime: Date.now() - startTime }; // Mark command as executed await this.updateCommandStatus(_id, 'executed', result); if (this.errorHandler) { await this.errorHandler.logInfo(`Command ${commandType} executed successfully`, context); } else { console.log(`Command ${commandType} executed successfully for license ${licenseKey}`); } return result; } catch (error) { result = { success: false, message: error.message || 'Command execution failed', data: { error: error.toString() }, executionTime: Date.now() - startTime }; // Mark command as failed await this.updateCommandStatus(_id, 'failed', result); if (this.errorHandler) { await this.errorHandler.handleError(error, { ...context, commandResult: result }); } else { console.error(`Command ${commandType} failed for license ${licenseKey}:`, error); } throw error; } } /** * Block the application * @param {string} reason - Reason for blocking * @returns {Promise<void>} */ async blockApplication(reason = 'License violation detected') { this.applicationBlocked = true; try { // Log the blocking event await this.logRemoteControlEvent('application_blocked', { reason, timestamp: new Date(), licenseKey: this.currentLicenseKey }); console.warn(`Application blocked: ${reason}`); // In a real implementation, this might terminate the process or disable functionality // For now, we'll set a flag that can be checked by the main application process.env.SDK_APPLICATION_BLOCKED = 'true'; process.env.SDK_BLOCK_REASON = reason; } catch (error) { console.error('Error blocking application:', error); throw error; } } /** * Update license status in the database * @param {string} status - New license status * @param {Object} additionalData - Additional data to store * @returns {Promise<void>} */ async updateLicenseStatus(status, additionalData = {}) { if (!this.currentLicenseKey) { throw new Error('No license key available for status update'); } try { const db = this.databaseManager.getMonitoringDB(); const collection = db.collection('licenses'); const updateData = { isActive: status === 'active', lastStatusUpdate: new Date(), ...additionalData }; const result = await collection.updateOne( { licenseKey: this.currentLicenseKey }, { $set: updateData } ); if (result.matchedCount === 0) { console.warn(`License not found for status update: ${this.currentLicenseKey}`); } else { console.log(`License status updated to ${status} for license: ${this.currentLicenseKey}`); } // Log the status change await this.logRemoteControlEvent('license_status_updated', { newStatus: status, licenseKey: this.currentLicenseKey, additionalData }); } catch (error) { console.error('Error updating license status:', error); throw error; } } /** * Start continuous command polling * @param {number} intervalMs - Polling interval in milliseconds (optional) */ startCommandPolling(intervalMs = this.pollingIntervalMs) { if (this.isPolling) { console.log('Command polling is already running'); return; } this.pollingIntervalMs = intervalMs; this.isPolling = true; console.log(`Starting command polling every ${intervalMs}ms for license: ${this.currentLicenseKey}`); this.pollingInterval = setInterval(async () => { try { const commands = await this.checkForCommands(); if (commands.length > 0) { console.log(`Found ${commands.length} pending command(s)`); // Execute commands in order of priority for (const command of commands) { try { await this.executeCommand(command); } catch (error) { console.error(`Failed to execute command ${command._id}:`, error); // Continue with next command even if one fails } } } } catch (error) { console.error('Error during command polling:', error); } }, intervalMs); } /** * Stop command polling */ stopCommandPolling() { if (this.pollingInterval) { clearInterval(this.pollingInterval); this.pollingInterval = null; } this.isPolling = false; console.log('Command polling stopped'); } /** * Check if application is currently blocked * @returns {boolean} */ isApplicationBlocked() { return this.applicationBlocked || process.env.SDK_APPLICATION_BLOCKED === 'true'; } /** * Get current block reason * @returns {string|null} */ getBlockReason() { return process.env.SDK_BLOCK_REASON || null; } // Command Handlers /** * Handle block command * @param {Object} parameters - Command parameters * @returns {Promise<Object>} */ async handleBlockCommand(parameters) { const reason = parameters.reason || 'Application blocked by remote command'; await this.blockApplication(reason); return { message: 'Application blocked successfully', data: { reason } }; } /** * Handle activate command * @param {Object} parameters - Command parameters * @returns {Promise<Object>} */ async handleActivateCommand(parameters) { await this.updateLicenseStatus('active', parameters); this.applicationBlocked = false; delete process.env.SDK_APPLICATION_BLOCKED; delete process.env.SDK_BLOCK_REASON; return { message: 'License activated successfully', data: { status: 'active' } }; } /** * Handle deactivate command * @param {Object} parameters - Command parameters * @returns {Promise<Object>} */ async handleDeactivateCommand(parameters) { await this.updateLicenseStatus('inactive', parameters); const reason = parameters.reason || 'License deactivated by remote command'; await this.blockApplication(reason); return { message: 'License deactivated successfully', data: { status: 'inactive' } }; } /** * Handle update command * @param {Object} parameters - Command parameters * @returns {Promise<Object>} */ async handleUpdateCommand(parameters) { // Update license with new parameters await this.updateLicenseStatus('active', parameters); return { message: 'License updated successfully', data: parameters }; } /** * Handle restart command * @param {Object} parameters - Command parameters * @returns {Promise<Object>} */ async handleRestartCommand(parameters) { // Log restart request await this.logRemoteControlEvent('restart_requested', parameters); // In a real implementation, this might restart the application console.log('Application restart requested via remote command'); return { message: 'Restart command acknowledged', data: { restartRequested: true } }; } /** * Handle status command * @param {Object} parameters - Command parameters * @returns {Promise<Object>} */ async handleStatusCommand(parameters) { const status = { licenseKey: this.currentLicenseKey, isBlocked: this.isApplicationBlocked(), blockReason: this.getBlockReason(), isPolling: this.isPolling, pollingInterval: this.pollingIntervalMs, timestamp: new Date() }; return { message: 'Status retrieved successfully', data: status }; } /** * Handle config command * @param {Object} parameters - Command parameters * @returns {Promise<Object>} */ async handleConfigCommand(parameters) { // Update configuration parameters if (parameters.pollingInterval && typeof parameters.pollingInterval === 'number') { this.pollingIntervalMs = parameters.pollingInterval; // Restart polling with new interval if currently polling if (this.isPolling) { this.stopCommandPolling(); this.startCommandPolling(this.pollingIntervalMs); } } return { message: 'Configuration updated successfully', data: parameters }; } // Utility Methods /** * Update command status in database * @param {string} commandId - Command ID * @param {string} status - New status * @param {Object} result - Execution result (optional) * @returns {Promise<void>} */ async updateCommandStatus(commandId, status, result = null) { try { const db = this.databaseManager.getMonitoringDB(); const collection = db.collection('remoteCommands'); const updateData = { status, ...(status === 'executing' && { executedAt: new Date() }), ...(result && { result }) }; await collection.updateOne( { _id: commandId }, { $set: updateData } ); } catch (error) { console.error('Error updating command status:', error); throw error; } } /** * Log remote control events * @param {string} event - Event type * @param {Object} data - Event data * @returns {Promise<void>} */ async logRemoteControlEvent(event, data) { try { const db = this.databaseManager.getMonitoringDB(); const collection = db.collection('usageLogs'); await collection.insertOne({ licenseKey: this.currentLicenseKey, action: `remote_control_${event}`, timestamp: new Date(), systemId: process.env.HOSTNAME || 'unknown', details: data, success: true }); } catch (error) { console.error('Error logging remote control event:', error); // Don't throw error for logging failures } } /** * Cleanup resources * @returns {Promise<void>} */ async cleanup() { const context = { component: 'remote-control-handler', operation: 'cleanup', licenseKey: this.currentLicenseKey }; try { this.stopCommandPolling(); if (this.databaseManager) { await this.databaseManager.closeConnection(); } if (this.errorHandler) { await this.errorHandler.logInfo('Remote control handler cleanup completed', context); } else { console.log('Remote control handler cleanup completed'); } } catch (error) { if (this.errorHandler) { await this.errorHandler.handleError(error, context); } else { console.error('Error during remote control handler cleanup:', error); } } finally { if (this.errorHandler) { await this.errorHandler.cleanup(); } } } } export default RemoteControlHandler;