UNPKG

@ked3/ktrace

Version:

跨平台埋点分析SDK

228 lines (194 loc) 5.89 kB
import { TrackEvent, TransportOptions } from './types'; import { isSupportBeacon, normalizeUrl } from './utils'; /** * 传输层类 * 负责事件数据的发送和缓存 */ class Transport { private options: TransportOptions; private queue: TrackEvent[] = []; private timer: number | null = null; private sending: boolean = false; private storage: Storage | null = null; private storageKey: string = 'ktrace_events'; /** * 构造函数 */ constructor(options: TransportOptions) { const defaultOptions: Partial<TransportOptions> = { maxBatchSize: 10, flushInterval: 5000, retryTimes: 3, useBeacon: true, headers: { 'Content-Type': 'application/json' } }; this.options = { ...defaultOptions, ...options }; this.options.serverUrl = normalizeUrl(this.options.serverUrl); // 初始化本地存储 try { this.storage = window.localStorage; this.loadFromStorage(); } catch (e) { console.warn('[KTrace] 无法访问localStorage:', e); this.storage = null; } // 设置定时发送 this.setupTimer(); } /** * 发送单个事件 */ public send(event: TrackEvent): void { this.queue.push(event); // 如果队列长度达到阈值,立即发送 if (this.queue.length >= (this.options.maxBatchSize || 10)) { this.flush(); } } /** * 刷新队列,发送所有事件 */ public flush(useBeacon: boolean = true): void { if (this.queue.length === 0 || this.sending) { return; } const events = [...this.queue]; this.queue = []; this.sending = true; // 存储到本地 this.saveToStorage(); // 检查是否使用Beacon API if ((useBeacon || (this.options.useBeacon && window.navigator.onLine === false)) && isSupportBeacon()) { this.sendByBeacon(events); } else { this.sendByXHR(events); } } /** * 使用XMLHttpRequest发送数据 */ private sendByXHR(events: TrackEvent[]): void { const xhr = new XMLHttpRequest(); let retryCount = 0; const send = () => { xhr.open('POST', this.options.serverUrl + 'collect', true); // 设置请求头 if (this.options.headers) { Object.keys(this.options.headers).forEach(key => { xhr.setRequestHeader(key, this.options.headers![key]); }); } xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { // 发送成功 this.sending = false; this.removeFromStorage(events); } else if (retryCount < (this.options.retryTimes || 3)) { console.log('重试', retryCount); // 重试 retryCount++; setTimeout(send, 1000 * retryCount); } else { // 重试失败,放回队列 this.queue = [...events, ...this.queue]; this.sending = false; this.saveToStorage(); } } }; xhr.onerror = () => { if (retryCount < (this.options.retryTimes || 3)) { // 重试 retryCount++; setTimeout(send, 1000 * retryCount); } else { // 重试失败,放回队列 this.queue = [...events, ...this.queue]; this.sending = false; this.saveToStorage(); } }; xhr.send(JSON.stringify(events)); }; send(); } /** * 使用Beacon API发送数据 */ private sendByBeacon(events: TrackEvent[]): void { const success = navigator.sendBeacon( this.options.serverUrl + 'collect', JSON.stringify(events) ); if (!success) { // 如果发送失败,放回队列等待下次发送 this.queue = [...events, ...this.queue]; } else { this.removeFromStorage(events); } this.sending = false; } /** * 设置定时发送器 */ private setupTimer(): void { if (this.timer) { clearInterval(this.timer); } this.timer = window.setInterval(() => { this.flush(); }, this.options.flushInterval || 5000) as unknown as number; } /** * 从本地存储中加载事件 */ private loadFromStorage(): void { if (!this.storage) return; try { const stored = this.storage.getItem(this.storageKey); if (stored) { const events = JSON.parse(stored) as TrackEvent[]; this.queue = [...events, ...this.queue]; } } catch (e) { console.warn('[KTrace] 从本地存储加载事件失败:', e); } } /** * 保存事件到本地存储 */ private saveToStorage(): void { if (!this.storage) return; try { console.log('saveToStorage', this.storage); this.storage.setItem(this.storageKey, JSON.stringify(this.queue)); } catch (e) { console.warn('[KTrace] 保存事件到本地存储失败:', e); } } /** * 从本地存储中删除已发送的事件 */ private removeFromStorage(events: TrackEvent[]): void { if (!this.storage) return; try { // 获取当前存储的事件 const stored = this.storage.getItem(this.storageKey); if (stored) { const storedEvents = JSON.parse(stored) as TrackEvent[]; // 创建一个事件ID集合,用于快速查找 const eventIds = new Set(events.map(e => e.id)); // 过滤掉已发送的事件 const remaining = storedEvents.filter(e => !eventIds.has(e.id)); // 更新存储 this.storage.setItem(this.storageKey, JSON.stringify(remaining)); } } catch (e) { console.warn('[KTrace] 从本地存储删除事件失败:', e); } } } export default Transport;