Commit initial
Chargement des données en local
This commit is contained in:
387
formations/memory-pictogrammes/assets/js/analytics.js
Normal file
387
formations/memory-pictogrammes/assets/js/analytics.js
Normal file
@@ -0,0 +1,387 @@
|
||||
/* ============================================
|
||||
ANALYTICS & EVENT TRACKING
|
||||
============================================ */
|
||||
|
||||
/**
|
||||
* Manager d'analytics pour tracker les événements
|
||||
*/
|
||||
class AnalyticsManager {
|
||||
constructor() {
|
||||
this.sessionId = generateUUID();
|
||||
this.events = [];
|
||||
this.enabled = true;
|
||||
this.endpoint = window.ANALYTICS_ENDPOINT || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistrer un événement
|
||||
*/
|
||||
track(eventName, data = {}) {
|
||||
if (!this.enabled) return;
|
||||
|
||||
const event = {
|
||||
type: eventName,
|
||||
timestamp: Date.now(),
|
||||
sessionId: this.sessionId,
|
||||
userId: getUserId(),
|
||||
playerName: getPlayerName(),
|
||||
data: data
|
||||
};
|
||||
|
||||
this.events.push(event);
|
||||
|
||||
// Logger en debug
|
||||
DEBUG.log(`Event tracked: ${eventName}`, data);
|
||||
|
||||
// Envoyer au serveur si configuré
|
||||
if (this.endpoint) {
|
||||
this.sendToServer(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Démarrage du jeu
|
||||
*/
|
||||
trackGameStart(difficulty) {
|
||||
this.track('game_started', {
|
||||
difficulty: difficulty,
|
||||
mode: DIFFICULTY_MODES[difficulty].name
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retournement de carte
|
||||
*/
|
||||
trackFlip(cardIndex, totalFlips) {
|
||||
this.track('card_flipped', {
|
||||
cardIndex: cardIndex,
|
||||
totalFlips: totalFlips
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Paire trouvée
|
||||
*/
|
||||
trackMatch(pictogrammeId, totalMatches, totalMoves) {
|
||||
this.track('pair_matched', {
|
||||
pictogrammeId: pictogrammeId,
|
||||
totalMatches: totalMatches,
|
||||
totalMoves: totalMoves
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Paire incorrecte
|
||||
*/
|
||||
trackMismatch(pictogramme1Id, pictogramme2Id, move) {
|
||||
this.track('pair_mismatched', {
|
||||
pictogramme1: pictogramme1Id,
|
||||
pictogramme2: pictogramme2Id,
|
||||
move: move
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fin du jeu
|
||||
*/
|
||||
trackGameEnd(gameData) {
|
||||
this.track('game_completed', {
|
||||
difficulty: gameData.difficulty,
|
||||
finalScore: gameData.finalScore,
|
||||
moves: gameData.moves,
|
||||
duration: gameData.duration,
|
||||
won: gameData.won,
|
||||
achievements: gameData.achievements || []
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Changement de difficulté
|
||||
*/
|
||||
trackDifficultyChange(newDifficulty) {
|
||||
this.track('difficulty_changed', {
|
||||
newDifficulty: newDifficulty
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Affichage du classement
|
||||
*/
|
||||
trackLeaderboardView(filter) {
|
||||
this.track('leaderboard_viewed', {
|
||||
filter: filter
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoyer les événements au serveur
|
||||
*/
|
||||
sendToServer(event) {
|
||||
if (!this.endpoint) return;
|
||||
|
||||
fetch(this.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(event)
|
||||
}).catch(err => {
|
||||
// Silencieusement échouer en offline
|
||||
DEBUG.log('Analytics offline:', err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoyer batch d'événements
|
||||
*/
|
||||
sendBatch() {
|
||||
if (!this.endpoint || this.events.length === 0) return;
|
||||
|
||||
fetch(this.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
batch: true,
|
||||
events: this.events,
|
||||
sessionId: this.sessionId
|
||||
})
|
||||
}).then(() => {
|
||||
this.events = []; // Vider après envoi
|
||||
DEBUG.log('Analytics batch sent');
|
||||
}).catch(err => {
|
||||
DEBUG.log('Analytics batch failed:', err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir le résumé de la session
|
||||
*/
|
||||
getSessionSummary() {
|
||||
return {
|
||||
sessionId: this.sessionId,
|
||||
userId: getUserId(),
|
||||
playerName: getPlayerName(),
|
||||
eventCount: this.events.length,
|
||||
events: this.events
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporter les données en JSON
|
||||
*/
|
||||
exportJSON() {
|
||||
const data = {
|
||||
exportDate: new Date().toISOString(),
|
||||
sessionSummary: this.getSessionSummary(),
|
||||
allEvents: this.events
|
||||
};
|
||||
return JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Télécharger les données
|
||||
*/
|
||||
downloadData() {
|
||||
const data = this.exportJSON();
|
||||
const blob = new Blob([data], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `analytics-${generateUUID().slice(0, 8)}.json`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nettoyer les événements
|
||||
*/
|
||||
clear() {
|
||||
this.events = [];
|
||||
DEBUG.log('Analytics cleared');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Heatmap Analytics - Tracker l'interaction avec les cartes
|
||||
*/
|
||||
class HeatmapAnalytics {
|
||||
constructor() {
|
||||
this.heatmap = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistrer un clic de carte
|
||||
*/
|
||||
recordCardClick(cardIndex) {
|
||||
if (!this.heatmap.has(cardIndex)) {
|
||||
this.heatmap.set(cardIndex, 0);
|
||||
}
|
||||
this.heatmap.set(cardIndex, this.heatmap.get(cardIndex) + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir les données de heatmap
|
||||
*/
|
||||
getHeatmap() {
|
||||
const sorted = Array.from(this.heatmap.entries())
|
||||
.sort((a, b) => b[1] - a[1]);
|
||||
|
||||
return Object.fromEntries(sorted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculer la carte la plus cliquée
|
||||
*/
|
||||
getMostClickedCard() {
|
||||
let maxClicks = 0;
|
||||
let maxCard = -1;
|
||||
|
||||
for (const [card, clicks] of this.heatmap) {
|
||||
if (clicks > maxClicks) {
|
||||
maxClicks = clicks;
|
||||
maxCard = card;
|
||||
}
|
||||
}
|
||||
|
||||
return { card: maxCard, clicks: maxClicks };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performance Monitor - Analyser les performances du jeu
|
||||
*/
|
||||
class PerformanceMonitor {
|
||||
constructor() {
|
||||
this.metrics = {};
|
||||
this.startTime = performance.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marquer un point de début
|
||||
*/
|
||||
mark(name) {
|
||||
this.metrics[name] = {
|
||||
start: performance.now(),
|
||||
end: null,
|
||||
duration: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Marquer la fin
|
||||
*/
|
||||
measure(name) {
|
||||
if (this.metrics[name]) {
|
||||
this.metrics[name].end = performance.now();
|
||||
this.metrics[name].duration = this.metrics[name].end - this.metrics[name].start;
|
||||
return this.metrics[name].duration;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir les métriques
|
||||
*/
|
||||
getMetrics() {
|
||||
return this.metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Afficher les métriques en console
|
||||
*/
|
||||
logMetrics() {
|
||||
console.table(this.metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* FPS Monitor
|
||||
*/
|
||||
monitorFPS(callback) {
|
||||
let lastTime = performance.now();
|
||||
let frames = 0;
|
||||
|
||||
const checkFPS = () => {
|
||||
frames++;
|
||||
const currentTime = performance.now();
|
||||
|
||||
if (currentTime - lastTime >= 1000) {
|
||||
const fps = frames;
|
||||
callback(fps);
|
||||
frames = 0;
|
||||
lastTime = currentTime;
|
||||
}
|
||||
|
||||
requestAnimationFrame(checkFPS);
|
||||
};
|
||||
|
||||
checkFPS();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A/B Testing Manager
|
||||
*/
|
||||
class ABTestingManager {
|
||||
constructor() {
|
||||
this.tests = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Créer un test A/B
|
||||
*/
|
||||
createTest(testName, variants) {
|
||||
const userId = getUserId();
|
||||
const variantIndex = parseInt(userId.slice(0, 8), 16) % variants.length;
|
||||
|
||||
this.tests[testName] = {
|
||||
variants: variants,
|
||||
selectedVariant: variants[variantIndex],
|
||||
variantIndex: variantIndex
|
||||
};
|
||||
|
||||
return this.tests[testName].selectedVariant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir le variant
|
||||
*/
|
||||
getVariant(testName) {
|
||||
return this.tests[testName]?.selectedVariant || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracker une conversion
|
||||
*/
|
||||
trackConversion(testName) {
|
||||
const test = this.tests[testName];
|
||||
if (test) {
|
||||
DEBUG.log(`Conversion tracked for test: ${testName}, variant: ${test.selectedVariant}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// INITIALISATION GLOBALES
|
||||
// ============================================
|
||||
|
||||
// Créer les managers globalement
|
||||
const analyticsManager = new AnalyticsManager();
|
||||
const heatmapAnalytics = new HeatmapAnalytics();
|
||||
const performanceMonitor = new PerformanceMonitor();
|
||||
const abTestingManager = new ABTestingManager();
|
||||
|
||||
// Exporter pour utilisation
|
||||
window.AnalyticsManager = AnalyticsManager;
|
||||
window.HeatmapAnalytics = HeatmapAnalytics;
|
||||
window.PerformanceMonitor = PerformanceMonitor;
|
||||
window.ABTestingManager = ABTestingManager;
|
||||
|
||||
// Event listener pour envoyer les analytics avant de quitter
|
||||
window.addEventListener('beforeunload', () => {
|
||||
analyticsManager.sendBatch();
|
||||
});
|
||||
|
||||
// Log de session au démarrage
|
||||
DEBUG.log('Analytics session started:', analyticsManager.sessionId);
|
||||
Reference in New Issue
Block a user