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
JavaScript
/**
* 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 };