import type { BaseEvent, CollectorConfig, UserInfo, CodeContext } from '@ide-collector/shared'; import { DEFAULT_COLLECTOR_CONFIG, generateAnonymousUserId } from '@ide-collector/shared'; import { EventQueue } from './storage/event-queue'; import { HttpReporter } from './reporters/http-reporter'; import { PrivacyHandler } from './privacy/privacy-handler'; import { ConfigManager } from './config/config-manager'; /** * IDE 数据采集器主类 */ export class Collector { private static instance: Collector | null = null; private config: CollectorConfig; private userInfo: UserInfo | null = null; private eventQueue: EventQueue; private reporter: HttpReporter; private privacyHandler: PrivacyHandler; private configManager: ConfigManager; private flushTimer: ReturnType | null = null; private isInitialized = false; private constructor(config: Partial = {}) { this.config = { ...DEFAULT_COLLECTOR_CONFIG, ...config }; this.eventQueue = new EventQueue(this.config.batchSize); this.reporter = new HttpReporter(this.config.apiEndpoint); this.privacyHandler = new PrivacyHandler(this.config.privacy); this.configManager = new ConfigManager(); } /** * 获取单例实例 */ public static getInstance(config?: Partial): Collector { if (!Collector.instance) { Collector.instance = new Collector(config); } return Collector.instance; } /** * 初始化采集器 */ public async initialize(userInfo: Omit): Promise { if (this.isInitialized) { return; } // 加载保存的配置 const savedConfig = await this.configManager.loadConfig(); if (savedConfig) { this.config = { ...this.config, ...savedConfig }; } // 生成或恢复用户ID const savedUserId = await this.configManager.getUserId(); this.userInfo = { ...userInfo, userId: savedUserId || generateAnonymousUserId(), }; // 保存用户ID if (!savedUserId) { await this.configManager.saveUserId(this.userInfo.userId); } // 恢复未发送的事件 const pendingEvents = await this.eventQueue.loadFromStorage(); if (pendingEvents.length > 0) { console.log(`[Collector] Restored ${pendingEvents.length} pending events`); } // 启动定时刷新 this.startFlushTimer(); this.isInitialized = true; console.log('[Collector] Initialized successfully'); } /** * 记录事件 */ public async trackEvent(event: Omit): Promise { if (!this.isInitialized || !this.config.enabled) { return; } // 检查采样率 if (Math.random() > this.config.samplingRate) { return; } // 检查事件类型是否需要采集 if (!this.config.eventsToCapture.includes(event.eventType)) { return; } // 应用隐私处理 const sanitizedEvent = this.privacyHandler.sanitizeEvent({ ...event, userInfo: this.userInfo!, }); // 添加到队列 await this.eventQueue.enqueue(sanitizedEvent); // 如果开启实时上报,立即发送 if (this.config.realtimeEnabled) { await this.flush(); } } /** * 强制刷新队列 */ public async flush(): Promise { if (!this.isInitialized) { return; } const events = await this.eventQueue.dequeueAll(); if (events.length === 0) { return; } try { await this.reporter.sendBatch(events); console.log(`[Collector] Successfully sent ${events.length} events`); } catch (error) { console.error('[Collector] Failed to send events:', error); // 重新入队失败的事件 for (const event of events) { await this.eventQueue.enqueue(event); } } } /** * 更新配置 */ public async updateConfig(config: Partial): Promise { this.config = { ...this.config, ...config }; this.privacyHandler = new PrivacyHandler(this.config.privacy); this.reporter = new HttpReporter(this.config.apiEndpoint); this.eventQueue.setMaxSize(this.config.batchSize); // 重启定时器 this.stopFlushTimer(); this.startFlushTimer(); // 保存配置 await this.configManager.saveConfig(this.config); } /** * 获取当前配置 */ public getConfig(): CollectorConfig { return { ...this.config }; } /** * 获取队列状态 */ public getQueueStatus(): { size: number; maxSize: number } { return { size: this.eventQueue.size(), maxSize: this.config.batchSize, }; } /** * 暂停采集 */ public pause(): void { this.config.enabled = false; this.stopFlushTimer(); } /** * 恢复采集 */ public resume(): void { this.config.enabled = true; this.startFlushTimer(); } /** * 销毁采集器 */ public async destroy(): Promise { this.stopFlushTimer(); await this.flush(); this.isInitialized = false; Collector.instance = null; } /** * 创建代码上下文 */ public createCodeContext(context: Partial): CodeContext { return this.privacyHandler.sanitizeContext({ filePath: context.filePath || '', language: context.language || 'unknown', ...context, }); } private startFlushTimer(): void { if (this.flushTimer) { return; } this.flushTimer = setInterval( () => this.flush(), this.config.flushIntervalSeconds * 1000 ); } private stopFlushTimer(): void { if (this.flushTimer) { clearInterval(this.flushTimer); this.flushTimer = null; } } }