realtimecursor
Version:
Real-time collaboration system with cursor tracking and approval workflow
230 lines (202 loc) • 5.66 kB
JavaScript
/**
* 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;