@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
JavaScript
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();
}
}