UNPKG

@optic7409/resolvo-analytics

Version:

Simplified analytics client library for Next.js with automatic SSR handling, one-line integration, and comprehensive tracking

230 lines (229 loc) 7.81 kB
import axios from 'axios'; export class AnalyticsClient { constructor(config) { this.eventQueue = []; this.isOnline = true; this.flushTimer = null; this.config = { apiUrl: 'https://www.resolvo.com/api/v1/analytics', debug: false, enableAutoTracking: true, ...config }; this.httpClient = axios.create({ baseURL: this.config.apiUrl, timeout: 10000, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.config.apiKey}`, 'X-Website-Token': this.config.websiteToken, 'X-Client-Version': '1.0.0' } }); this.sessionData = this.initializeSession(); this.setupEventListeners(); this.startPeriodicFlush(); if (this.config.debug) { console.log('[Resolvo Analytics] Initialized with config:', this.config); } } initializeSession() { const sessionId = this.config.sessionId || this.generateSessionId(); return { sessionId, startTime: Date.now(), lastActivity: Date.now(), pageViews: 0, events: 0, referrer: typeof document !== 'undefined' ? document.referrer : undefined, userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'Unknown', screen: { width: typeof window !== 'undefined' ? window.screen.width : 0, height: typeof window !== 'undefined' ? window.screen.height : 0 } }; } generateSessionId() { return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } setupEventListeners() { if (typeof window === 'undefined') return; // Online/offline detection window.addEventListener('online', () => { this.isOnline = true; this.flushQueue(); }); window.addEventListener('offline', () => { this.isOnline = false; }); // Auto-tracking setup if (this.config.enableAutoTracking) { this.setupAutoTracking(); } } setupAutoTracking() { if (typeof window === 'undefined') return; // Track page visibility changes document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') { this.flushQueue(); } }); // Track beforeunload window.addEventListener('beforeunload', () => { this.flushQueue(); }); // Track clicks on important elements document.addEventListener('click', (event) => { const target = event.target; if (target.tagName === 'A' || target.tagName === 'BUTTON' || target.hasAttribute('data-track')) { this.trackClick({ element: target.tagName.toLowerCase(), text: target.textContent?.trim(), url: target.getAttribute('href') || undefined, position: { x: event.clientX, y: event.clientY } }); } }); } startPeriodicFlush() { this.flushTimer = setInterval(() => { if (this.eventQueue.length > 0) { this.flushQueue(); } }, 5000); // Flush every 5 seconds } async track(eventName, properties) { const event = { type: 'custom', name: eventName, properties, timestamp: Date.now(), userId: this.config.userId, sessionId: this.sessionData.sessionId }; await this.sendEvent(event); } async trackPageView(url, title) { const pageUrl = url || (typeof window !== 'undefined' ? window.location.href : ''); const pageTitle = title || (typeof document !== 'undefined' ? document.title : ''); const event = { type: 'page_view', url: pageUrl, title: pageTitle, referrer: typeof document !== 'undefined' ? document.referrer : undefined, timestamp: Date.now(), userId: this.config.userId, sessionId: this.sessionData.sessionId }; this.sessionData.pageViews++; this.sessionData.lastActivity = Date.now(); await this.sendEvent(event); } async trackClick(data) { const event = { type: 'click', ...data, timestamp: Date.now(), userId: this.config.userId, sessionId: this.sessionData.sessionId }; await this.sendEvent(event); } async identify(userId, properties) { this.config.userId = userId; try { const response = await this.httpClient.post('/identify', { userId, properties, sessionId: this.sessionData.sessionId, timestamp: Date.now() }); if (this.config.debug) { console.log('[Resolvo Analytics] User identified:', response.data); } } catch (error) { if (this.config.debug) { console.error('[Resolvo Analytics] Failed to identify user:', error); } } } async sendEvent(event) { this.sessionData.events++; this.sessionData.lastActivity = Date.now(); if (!this.isOnline) { this.queueEvent(event); return; } try { const response = await this.httpClient.post('/events', { event, session: this.sessionData }); if (this.config.debug) { console.log('[Resolvo Analytics] Event sent:', event.type, response.data); } } catch (error) { if (this.config.debug) { console.error('[Resolvo Analytics] Failed to send event:', error); } this.queueEvent(event); } } queueEvent(event) { this.eventQueue.push({ event, retries: 0, timestamp: Date.now() }); // Limit queue size to prevent memory issues if (this.eventQueue.length > 100) { this.eventQueue.shift(); } } async flushQueue() { if (!this.isOnline || this.eventQueue.length === 0) return; const eventsToSend = [...this.eventQueue]; this.eventQueue = []; try { const response = await this.httpClient.post('/events/batch', { events: eventsToSend.map(qe => qe.event), session: this.sessionData }); if (this.config.debug) { console.log(`[Resolvo Analytics] Batch sent: ${eventsToSend.length} events`, response.data); } } catch (error) { if (this.config.debug) { console.error('[Resolvo Analytics] Failed to send batch:', error); } // Re-queue events with retry logic eventsToSend.forEach(queuedEvent => { if (queuedEvent.retries < 3) { this.eventQueue.push({ ...queuedEvent, retries: queuedEvent.retries + 1 }); } }); } } getSessionId() { return this.sessionData.sessionId; } getUserId() { return this.config.userId; } destroy() { if (this.flushTimer) { clearInterval(this.flushTimer); } this.flushQueue(); } }