@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
JavaScript
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;