advanced-games-library
Version:
Advanced Gaming Library for React Native - Four Complete Games with iOS Compatibility Fixes
363 lines (323 loc) • 8.59 kB
text/typescript
import { AnalyticsEvent } from '../core/types';
/**
* AnalyticsService - handles event tracking and analytics
*/
export class AnalyticsService {
private isEnabled: boolean;
private eventQueue: AnalyticsEvent[] = [];
private isOnline: boolean = true;
private flushInterval?: NodeJS.Timeout;
private readonly QUEUE_LIMIT = 100;
private readonly FLUSH_INTERVAL = 30000; // 30 seconds
constructor(enabled: boolean = true) {
this.isEnabled = enabled;
}
/**
* Initialize the analytics service
*/
async initialize(): Promise<void> {
if (!this.isEnabled) {
return;
}
// Set up periodic flush
this.flushInterval = setInterval(() => {
this.flush();
}, this.FLUSH_INTERVAL);
// Load queued events from storage
await this.loadQueuedEvents();
// Listen for network state changes (if available)
this.setupNetworkListener();
}
/**
* Track an analytics event
*/
trackEvent(event: AnalyticsEvent): void {
if (!this.isEnabled) {
return;
}
// Add timestamp if not provided
if (!event.timestamp) {
event.timestamp = new Date();
}
// Add to queue
this.eventQueue.push(event);
// Limit queue size
if (this.eventQueue.length > this.QUEUE_LIMIT) {
this.eventQueue = this.eventQueue.slice(-this.QUEUE_LIMIT);
}
// Try to flush if online
if (this.isOnline) {
this.flush();
}
}
/**
* Track game start
*/
trackGameStart(gameId: string, properties: Record<string, any> = {}): void {
this.trackEvent({
event: 'game_started',
gameId,
properties: {
...properties,
timestamp: new Date()
},
timestamp: new Date()
});
}
/**
* Track game completion
*/
trackGameComplete(gameId: string, result: any, properties: Record<string, any> = {}): void {
this.trackEvent({
event: 'game_completed',
gameId,
properties: {
...properties,
score: result.score,
timeSpent: result.timeSpent,
completed: result.completed,
difficulty: result.difficulty
},
timestamp: new Date()
});
}
/**
* Track custom event
*/
trackCustomEvent(eventName: string, properties: Record<string, any> = {}): void {
this.trackEvent({
event: eventName,
properties,
timestamp: new Date()
});
}
/**
* Track error
*/
trackError(error: Error | string, context: Record<string, any> = {}): void {
this.trackEvent({
event: 'error_occurred',
properties: {
error: typeof error === 'string' ? error : error.message,
stack: typeof error === 'object' ? error.stack : undefined,
...context
},
timestamp: new Date()
});
}
/**
* Track performance metric
*/
trackPerformance(metric: string, value: number, unit: string = 'ms'): void {
this.trackEvent({
event: 'performance_metric',
properties: {
metric,
value,
unit,
timestamp: new Date()
},
timestamp: new Date()
});
}
/**
* Flush queued events
*/
async flush(): Promise<void> {
if (!this.isEnabled || this.eventQueue.length === 0) {
return;
}
const eventsToSend = [...this.eventQueue];
this.eventQueue = [];
try {
await this.sendEvents(eventsToSend);
} catch (error) {
console.warn('Failed to send analytics events:', error);
// Add events back to queue on failure
this.eventQueue = [...eventsToSend, ...this.eventQueue];
// Limit queue size
if (this.eventQueue.length > this.QUEUE_LIMIT) {
this.eventQueue = this.eventQueue.slice(-this.QUEUE_LIMIT);
}
}
}
/**
* Enable/disable analytics
*/
setEnabled(enabled: boolean): void {
this.isEnabled = enabled;
if (!enabled) {
this.eventQueue = [];
if (this.flushInterval) {
clearInterval(this.flushInterval);
this.flushInterval = undefined;
}
} else if (!this.flushInterval) {
this.flushInterval = setInterval(() => {
this.flush();
}, this.FLUSH_INTERVAL);
}
}
/**
* Get analytics statistics
*/
getStats(): {
queuedEvents: number;
isEnabled: boolean;
isOnline: boolean;
} {
return {
queuedEvents: this.eventQueue.length,
isEnabled: this.isEnabled,
isOnline: this.isOnline
};
}
/**
* Private methods
*/
private async sendEvents(events: AnalyticsEvent[]): Promise<void> {
// This is where you would send events to your analytics service
// For example: Firebase Analytics, Mixpanel, custom backend, etc.
// For now, just log to console in development
if (__DEV__) {
console.log('Analytics Events:', events);
}
// Example implementation for a custom backend:
/*
try {
await fetch('https://your-analytics-endpoint.com/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(events),
});
} catch (error) {
throw new Error(`Failed to send analytics: ${error}`);
}
*/
// For Firebase Analytics:
/*
try {
const analytics = await import('@react-native-firebase/analytics');
for (const event of events) {
await analytics.default().logEvent(event.event, event.properties);
}
} catch (error) {
throw new Error(`Failed to send to Firebase: ${error}`);
}
*/
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 100));
}
private async loadQueuedEvents(): Promise<void> {
try {
// Try to load events from storage (implement based on your storage solution)
// const { StorageService } = await import('./StorageService');
// const storage = new StorageService();
// const queuedEvents = await storage.getData('analytics_queue');
// if (queuedEvents) {
// this.eventQueue = queuedEvents;
// }
} catch (error) {
console.warn('Failed to load queued analytics events:', error);
}
}
private setupNetworkListener(): void {
try {
// Listen for network state changes (React Native NetInfo)
// const NetInfo = require('@react-native-community/netinfo');
// NetInfo.addEventListener(state => {
// this.isOnline = state.isConnected;
// if (this.isOnline && this.eventQueue.length > 0) {
// this.flush();
// }
// });
} catch (error) {
console.warn('Network listener not available:', error);
}
}
/**
* Cleanup
*/
destroy(): void {
if (this.flushInterval) {
clearInterval(this.flushInterval);
this.flushInterval = undefined;
}
// Final flush
this.flush();
}
}
/**
* Analytics event builders for common events
*/
export class AnalyticsEventBuilder {
static gameStarted(gameId: string, properties: Record<string, any> = {}): AnalyticsEvent {
return {
event: 'game_started',
gameId,
properties: {
...properties,
timestamp: new Date()
},
timestamp: new Date()
};
}
static gameCompleted(gameId: string, result: any): AnalyticsEvent {
return {
event: 'game_completed',
gameId,
properties: {
score: result.score,
timeSpent: result.timeSpent,
completed: result.completed,
difficulty: result.difficulty
},
timestamp: new Date()
};
}
static scoreAchieved(gameId: string, score: number, isHighScore: boolean = false): AnalyticsEvent {
return {
event: 'score_achieved',
gameId,
properties: {
score,
isHighScore
},
timestamp: new Date()
};
}
static achievementUnlocked(achievementId: string, gameId?: string): AnalyticsEvent {
return {
event: 'achievement_unlocked',
gameId,
properties: {
achievementId
},
timestamp: new Date()
};
}
static userAction(action: string, gameId?: string, properties: Record<string, any> = {}): AnalyticsEvent {
return {
event: 'user_action',
gameId,
properties: {
action,
...properties
},
timestamp: new Date()
};
}
static performanceMetric(metric: string, value: number, gameId?: string): AnalyticsEvent {
return {
event: 'performance',
gameId,
properties: {
metric,
value
},
timestamp: new Date()
};
}
}