UNPKG

realtimecursor

Version:

Real-time collaboration system with cursor tracking and approval workflow

230 lines (202 loc) 5.66 kB
/** * Analytics Service for RealtimeCursor * Tracks user activity, connections, and project usage */ class AnalyticsService { static connections = new Map(); static projectJoins = new Map(); static contentUpdates = new Map(); static userActivity = new Map(); static apiKeyUsage = new Map(); /** * Track a new connection */ static trackConnection(socketId, apiKey, projectId, userId) { this.connections.set(socketId, { apiKey, projectId, userId, connectedAt: Date.now() }); // Track API key usage if (!this.apiKeyUsage.has(apiKey)) { this.apiKeyUsage.set(apiKey, { connections: 0, projects: new Set(), users: new Set() }); } const apiKeyStats = this.apiKeyUsage.get(apiKey); apiKeyStats.connections++; apiKeyStats.projects.add(projectId); apiKeyStats.users.add(userId); } /** * Track a disconnection */ static trackDisconnection(socketId) { const connection = this.connections.get(socketId); if (connection) { connection.disconnectedAt = Date.now(); connection.duration = connection.disconnectedAt - connection.connectedAt; this.connections.delete(socketId); } } /** * Record a project join */ static recordProjectJoin(projectId, userId) { const key = `${projectId}:${userId}`; this.projectJoins.set(key, { projectId, userId, joinedAt: Date.now() }); // Track user activity if (!this.userActivity.has(userId)) { this.userActivity.set(userId, { projects: new Set(), lastActive: Date.now(), totalTime: 0 }); } const userStats = this.userActivity.get(userId); userStats.projects.add(projectId); userStats.lastActive = Date.now(); } /** * Record a project leave */ static recordProjectLeave(projectId, userId) { const key = `${projectId}:${userId}`; const join = this.projectJoins.get(key); if (join) { join.leftAt = Date.now(); join.duration = join.leftAt - join.joinedAt; this.projectJoins.delete(key); // Update user activity if (this.userActivity.has(userId)) { const userStats = this.userActivity.get(userId); userStats.totalTime += join.duration; userStats.lastActive = Date.now(); } } } /** * Track content updates */ static trackContentUpdate(projectId, userId, contentLength) { const key = `${projectId}:${userId}`; if (!this.contentUpdates.has(key)) { this.contentUpdates.set(key, { projectId, userId, updates: 0, lastUpdate: null, contentLength: 0 }); } const update = this.contentUpdates.get(key); update.updates++; update.lastUpdate = Date.now(); update.contentLength = contentLength; // Update user activity if (this.userActivity.has(userId)) { const userStats = this.userActivity.get(userId); userStats.lastActive = Date.now(); } } /** * Get overall analytics */ static getAnalytics() { return { connections: { current: this.connections.size, total: this.connections.size + Array.from(this.connections.values()) .filter(c => c.disconnectedAt).length }, projects: { active: new Set(Array.from(this.connections.values()) .map(c => c.projectId)).size }, users: { active: new Set(Array.from(this.connections.values()) .map(c => c.userId)).size, total: this.userActivity.size }, apiKeys: { active: this.apiKeyUsage.size } }; } /** * Get analytics for a specific user */ static getUserAnalytics(userId) { const userStats = this.userActivity.get(userId); if (!userStats) { return null; } return { userId, projects: Array.from(userStats.projects), lastActive: userStats.lastActive, totalTime: userStats.totalTime, connections: Array.from(this.connections.values()) .filter(c => c.userId === userId).length }; } /** * Get analytics for a specific project */ static getProjectAnalytics(projectId) { const projectUsers = new Set(); let totalUpdates = 0; let totalTime = 0; // Count users who joined this project for (const [key, join] of this.projectJoins.entries()) { if (join.projectId === projectId) { projectUsers.add(join.userId); if (join.duration) { totalTime += join.duration; } } } // Count content updates for this project for (const [key, update] of this.contentUpdates.entries()) { if (update.projectId === projectId) { totalUpdates += update.updates; } } // Count current connections to this project const currentConnections = Array.from(this.connections.values()) .filter(c => c.projectId === projectId).length; return { projectId, users: Array.from(projectUsers), userCount: projectUsers.size, currentConnections, totalUpdates, totalTime }; } /** * Get analytics for a specific API key */ static getApiKeyAnalytics(apiKey) { const apiKeyStats = this.apiKeyUsage.get(apiKey); if (!apiKeyStats) { return null; } return { apiKey, connections: apiKeyStats.connections, projects: Array.from(apiKeyStats.projects), users: Array.from(apiKeyStats.users), projectCount: apiKeyStats.projects.size, userCount: apiKeyStats.users.size }; } } module.exports = AnalyticsService;