UNPKG

ssh-bridge-ai

Version:

One Command Magic SSH with Invisible Analytics - Connect to any server instantly with 'sshbridge user@server'. Zero setup, zero friction, pure magic. Industry-standard security with behind-the-scenes business intelligence.

269 lines (235 loc) 6.34 kB
/** * SSHBridge Analytics System * * Steve Jobs' approach: Track everything, affect nothing * Users never see this, but we get rich business intelligence */ const os = require('os'); const crypto = require('crypto'); class AnalyticsTracker { constructor() { this.sessionId = this.generateSessionId(); this.startTime = Date.now(); this.events = []; this.analyticsEndpoint = process.env.SSHBRIDGE_ANALYTICS_URL || 'https://analytics.sshbridge.dev'; // Track session start this.track('session_start', { platform: os.platform(), arch: os.arch(), nodeVersion: process.version, timestamp: new Date().toISOString() }); } /** * Track any event without affecting user experience */ track(event, data = {}) { try { const eventData = { event, timestamp: new Date().toISOString(), sessionId: this.sessionId, duration: Date.now() - this.startTime, ...data }; // Store locally for batch sending this.events.push(eventData); // Send immediately for critical events if (this.isCriticalEvent(event)) { this.sendAnalytics(eventData); } // Batch send other events if (this.events.length >= 10) { this.sendBatchAnalytics(); } } catch (error) { // Analytics failures should NEVER affect user experience console.debug('Analytics error (ignored):', error.message); } } /** * Track SSH connection attempts */ trackConnection(server, success, details = {}) { this.track('ssh_connection', { server, success, hasKeys: details.hasKeys || false, keyType: details.keyType || 'none', duration: details.duration || 0, error: details.error || null }); } /** * Track command execution */ trackCommand(server, command, success, duration, outputSize) { this.track('command_execution', { server, command: this.sanitizeCommand(command), success, duration, outputSize, timestamp: new Date().toISOString() }); } /** * Track session metrics */ trackSession(server, duration, commandsExecuted, totalOutputSize) { this.track('session_end', { server, duration, commandsExecuted, totalOutputSize, timestamp: new Date().toISOString() }); } /** * Track performance metrics */ trackPerformance(operation, duration, success, details = {}) { this.track('performance', { operation, duration, success, details }); } /** * Track user behavior patterns */ trackUserBehavior(action, context = {}) { this.track('user_behavior', { action, context, timestamp: new Date().toISOString() }); } /** * Track system health */ trackSystemHealth(metrics = {}) { this.track('system_health', { memoryUsage: process.memoryUsage(), cpuUsage: process.cpuUsage(), uptime: process.uptime(), ...metrics }); } /** * Send analytics data (fire and forget) */ async sendAnalytics(data) { try { // Don't wait for response - don't block user fetch(`${this.analyticsEndpoint}/track`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }).catch(() => {}); // Ignore all errors } catch (error) { // Analytics failures should NEVER affect user experience console.debug('Analytics send error (ignored):', error.message); } } /** * Send batch analytics */ async sendBatchAnalytics() { if (this.events.length === 0) return; try { const batchData = { sessionId: this.sessionId, events: this.events.splice(0, 10), // Remove sent events timestamp: new Date().toISOString() }; // Don't wait for response - don't block user fetch(`${this.analyticsEndpoint}/batch`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(batchData) }).catch(() => {}); // Ignore all errors } catch (error) { // Analytics failures should NEVER affect user experience console.debug('Analytics batch error (ignored):', error.message); } } /** * Determine if event should be sent immediately */ isCriticalEvent(event) { const criticalEvents = [ 'session_start', 'ssh_connection', 'session_end', 'error' ]; return criticalEvents.includes(event); } /** * Sanitize commands for analytics (remove sensitive data) */ sanitizeCommand(command) { if (!command) return ''; // Remove potential sensitive information let sanitized = command .replace(/password\s+\S+/gi, 'password [REDACTED]') .replace(/--password\s+\S+/gi, '--password [REDACTED]') .replace(/passwd\s+\S+/gi, 'passwd [REDACTED]') .replace(/sudo\s+password\s+\S+/gi, 'sudo password [REDACTED]') .replace(/ssh\s+-i\s+\S+/gi, 'ssh -i [KEY_FILE]') .replace(/--key\s+\S+/gi, '--key [KEY_FILE]'); // Limit length if (sanitized.length > 100) { sanitized = sanitized.substring(0, 97) + '...'; } return sanitized; } /** * Generate unique session ID */ generateSessionId() { return crypto.randomBytes(16).toString('hex'); } /** * Get analytics summary for this session */ getSessionSummary() { return { sessionId: this.sessionId, duration: Date.now() - this.startTime, eventCount: this.events.length, startTime: new Date(this.startTime).toISOString() }; } /** * Flush remaining events before shutdown */ async flush() { if (this.events.length > 0) { await this.sendBatchAnalytics(); } } } // Global analytics instance let analyticsInstance = null; function getAnalytics() { if (!analyticsInstance) { analyticsInstance = new AnalyticsTracker(); } return analyticsInstance; } // Track process exit process.on('exit', () => { if (analyticsInstance) { analyticsInstance.flush(); } }); process.on('SIGINT', () => { if (analyticsInstance) { analyticsInstance.flush(); } process.exit(0); }); module.exports = { AnalyticsTracker, getAnalytics };