BBPlease / src /static /app-analytics.js
hamza-ks's picture
Relaunch deploy: RF v5 (72.6%), voc2vec fallback, telemetry removed, secrets docs removed
84e6d52
Raw
History Blame Contribute Delete
7.58 kB
/**
* App Analytics and Version Tracking
* Tracks app usage, errors, and provides version information
*/
(function() {
'use strict';
const ANALYTICS_KEY = 'bbplease_analytics';
const USER_ID_KEY = 'bbplease_user_id';
const APP_VERSION = '1.0.0';
/**
* Generate or retrieve user ID
*/
function getUserId() {
let userId = localStorage.getItem(USER_ID_KEY);
if (!userId) {
userId = 'user_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
localStorage.setItem(USER_ID_KEY, userId);
}
return userId;
}
/**
* Get app version
*/
function getAppVersion() {
return window.APP_CONFIG ? window.APP_CONFIG.VERSION : APP_VERSION;
}
/**
* Get platform info
*/
function getPlatformInfo() {
const isCapacitor = window.Capacitor !== undefined;
const platform = isCapacitor ? window.Capacitor.getPlatform() : 'web';
return {
platform: platform,
isMobile: isCapacitor && (platform === 'android' || platform === 'ios'),
userAgent: navigator.userAgent,
language: navigator.language,
online: navigator.onLine
};
}
/**
* Track event
*/
function trackEvent(eventName, eventData = {}) {
const event = {
name: eventName,
timestamp: new Date().toISOString(),
userId: getUserId(),
version: getAppVersion(),
platform: getPlatformInfo(),
data: eventData
};
// Store locally (could be sent to server later)
const analytics = getAnalytics();
analytics.events.push(event);
// Keep only last 100 events
if (analytics.events.length > 100) {
analytics.events = analytics.events.slice(-100);
}
saveAnalytics(analytics);
console.log('📊 Event tracked:', eventName, eventData);
return event;
}
/**
* Track error
*/
function trackError(error, context = {}) {
const errorEvent = {
name: 'error',
timestamp: new Date().toISOString(),
userId: getUserId(),
version: getAppVersion(),
platform: getPlatformInfo(),
error: {
message: error.message || String(error),
stack: error.stack,
name: error.name,
context: context
}
};
const analytics = getAnalytics();
analytics.errors.push(errorEvent);
// Keep only last 50 errors
if (analytics.errors.length > 50) {
analytics.errors = analytics.errors.slice(-50);
}
saveAnalytics(analytics);
console.error('❌ Error tracked:', errorEvent);
// Optionally send to server
sendErrorToServer(errorEvent);
return errorEvent;
}
/**
* Send error to server (optional)
*/
async function sendErrorToServer(errorEvent) {
try {
if (!navigator.onLine) {
return; // Skip if offline
}
const url = (window.APP_CONFIG && window.APP_CONFIG.getApiUrl)
? window.APP_CONFIG.getApiUrl('/api/analytics/error')
: '/api/analytics/error';
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorEvent)
});
} catch (e) {
console.warn('Failed to send error to server:', e);
}
}
/**
* Get analytics data
*/
function getAnalytics() {
try {
const data = localStorage.getItem(ANALYTICS_KEY);
return data ? JSON.parse(data) : {
userId: getUserId(),
version: getAppVersion(),
firstLaunch: new Date().toISOString(),
events: [],
errors: [],
sessionCount: 0
};
} catch (e) {
return {
userId: getUserId(),
version: getAppVersion(),
firstLaunch: new Date().toISOString(),
events: [],
errors: [],
sessionCount: 0
};
}
}
/**
* Save analytics data
*/
function saveAnalytics(analytics) {
try {
localStorage.setItem(ANALYTICS_KEY, JSON.stringify(analytics));
} catch (e) {
console.error('Failed to save analytics:', e);
}
}
/**
* Track session start
*/
function trackSessionStart() {
const analytics = getAnalytics();
analytics.sessionCount = (analytics.sessionCount || 0) + 1;
analytics.lastSession = new Date().toISOString();
saveAnalytics(analytics);
trackEvent('session_start', {
sessionNumber: analytics.sessionCount
});
}
/**
* Get analytics summary
*/
function getAnalyticsSummary() {
const analytics = getAnalytics();
return {
userId: analytics.userId || getUserId(),
version: analytics.version || getAppVersion(),
firstLaunch: analytics.firstLaunch,
sessionCount: analytics.sessionCount || 0,
eventCount: analytics.events ? analytics.events.length : 0,
errorCount: analytics.errors ? analytics.errors.length : 0,
platform: getPlatformInfo()
};
}
/**
* Clear analytics (privacy)
*/
function clearAnalytics() {
localStorage.removeItem(ANALYTICS_KEY);
console.log('🗑️ Analytics cleared');
}
/**
* Initialize analytics
*/
function init() {
trackSessionStart();
// Track page load
trackEvent('app_loaded', {
platform: getPlatformInfo()
});
// Track online/offline changes
window.addEventListener('online', () => {
trackEvent('network_online');
});
window.addEventListener('offline', () => {
trackEvent('network_offline');
});
// Global error handler
window.addEventListener('error', (event) => {
trackError(event.error || new Error(event.message), {
filename: event.filename,
lineno: event.lineno,
colno: event.colno
});
});
// Unhandled promise rejection handler
window.addEventListener('unhandledrejection', (event) => {
trackError(event.reason, {
type: 'unhandled_promise_rejection'
});
});
console.log('📊 Analytics initialized');
}
// Export to window
window.AppAnalytics = {
trackEvent: trackEvent,
trackError: trackError,
getAnalyticsSummary: getAnalyticsSummary,
getUserId: getUserId,
getAppVersion: getAppVersion,
getPlatformInfo: getPlatformInfo,
clearAnalytics: clearAnalytics
};
// Auto-initialize
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();