- Correction des erreurs TypeScript dans userService.ts et workflowTester.ts - Ajout des propriétés manquantes aux objets User mockés - Conversion des dates de string vers objets Date - Correction des appels asynchrones et des types incompatibles - Ajout de dynamic rendering pour résoudre les erreurs useSearchParams - Enveloppement de useSearchParams dans Suspense boundary - Configuration de force-dynamic au niveau du layout principal Build réussi: 126 pages générées avec succès 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
278 lines
6.8 KiB
TypeScript
278 lines
6.8 KiB
TypeScript
/**
|
|
* Service de monitoring avancé côté client
|
|
*/
|
|
|
|
export interface PerformanceMetric {
|
|
name: string;
|
|
value: number;
|
|
timestamp: number;
|
|
tags?: Record<string, string>;
|
|
}
|
|
|
|
export interface ErrorMetric {
|
|
error: string;
|
|
stack?: string;
|
|
url: string;
|
|
userAgent: string;
|
|
timestamp: number;
|
|
userId?: string;
|
|
}
|
|
|
|
export interface UserAction {
|
|
action: string;
|
|
component: string;
|
|
timestamp: number;
|
|
duration?: number;
|
|
metadata?: Record<string, any>;
|
|
}
|
|
|
|
export class MonitoringService {
|
|
private static metrics: PerformanceMetric[] = [];
|
|
private static errors: ErrorMetric[] = [];
|
|
private static userActions: UserAction[] = [];
|
|
private static isEnabled = true;
|
|
|
|
/**
|
|
* Initialise le service de monitoring
|
|
*/
|
|
static init(): void {
|
|
if (typeof window === 'undefined') return;
|
|
|
|
// Monitoring des erreurs JavaScript
|
|
window.addEventListener('error', (event) => {
|
|
this.recordError({
|
|
error: event.message,
|
|
stack: event.error?.stack,
|
|
url: window.location.href,
|
|
userAgent: navigator.userAgent,
|
|
timestamp: Date.now()
|
|
});
|
|
});
|
|
|
|
// Monitoring des promesses rejetées
|
|
window.addEventListener('unhandledrejection', (event) => {
|
|
this.recordError({
|
|
error: `Unhandled Promise Rejection: ${event.reason}`,
|
|
url: window.location.href,
|
|
userAgent: navigator.userAgent,
|
|
timestamp: Date.now()
|
|
});
|
|
});
|
|
|
|
// Monitoring des performances de navigation
|
|
if ('performance' in window) {
|
|
window.addEventListener('load', () => {
|
|
setTimeout(() => {
|
|
this.recordNavigationMetrics();
|
|
}, 0);
|
|
});
|
|
}
|
|
|
|
// Envoi périodique des métriques
|
|
setInterval(() => {
|
|
this.sendMetrics();
|
|
}, 60000); // Toutes les minutes
|
|
}
|
|
|
|
/**
|
|
* Enregistre une métrique de performance
|
|
*/
|
|
static recordMetric(name: string, value: number, tags?: Record<string, string>): void {
|
|
if (!this.isEnabled) return;
|
|
|
|
this.metrics.push({
|
|
name,
|
|
value,
|
|
timestamp: Date.now(),
|
|
tags
|
|
});
|
|
|
|
// Limiter le nombre de métriques en mémoire
|
|
if (this.metrics.length > 1000) {
|
|
this.metrics = this.metrics.slice(-500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enregistre une erreur
|
|
*/
|
|
static recordError(error: ErrorMetric): void {
|
|
if (!this.isEnabled) return;
|
|
|
|
this.errors.push(error);
|
|
|
|
// Limiter le nombre d'erreurs en mémoire
|
|
if (this.errors.length > 100) {
|
|
this.errors = this.errors.slice(-50);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enregistre une action utilisateur
|
|
*/
|
|
static recordUserAction(action: string, component: string, metadata?: Record<string, any>): void {
|
|
if (!this.isEnabled) return;
|
|
|
|
this.userActions.push({
|
|
action,
|
|
component,
|
|
timestamp: Date.now(),
|
|
metadata
|
|
});
|
|
|
|
// Limiter le nombre d'actions en mémoire
|
|
if (this.userActions.length > 500) {
|
|
this.userActions = this.userActions.slice(-250);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mesure le temps d'exécution d'une fonction
|
|
*/
|
|
static async measureTime<T>(
|
|
name: string,
|
|
fn: () => Promise<T>,
|
|
tags?: Record<string, string>
|
|
): Promise<T> {
|
|
const start = performance.now();
|
|
try {
|
|
const result = await fn();
|
|
const duration = performance.now() - start;
|
|
this.recordMetric(`${name}.duration`, duration, tags);
|
|
this.recordMetric(`${name}.success`, 1, tags);
|
|
return result;
|
|
} catch (error) {
|
|
const duration = performance.now() - start;
|
|
this.recordMetric(`${name}.duration`, duration, tags);
|
|
this.recordMetric(`${name}.error`, 1, tags);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enregistre les métriques de navigation
|
|
*/
|
|
private static recordNavigationMetrics(): void {
|
|
if (!('performance' in window) || !window.performance.timing) return;
|
|
|
|
const timing = window.performance.timing;
|
|
const navigation = window.performance.navigation;
|
|
|
|
// Temps de chargement de la page
|
|
const pageLoadTime = timing.loadEventEnd - timing.navigationStart;
|
|
this.recordMetric('page.load_time', pageLoadTime, {
|
|
url: window.location.pathname
|
|
});
|
|
|
|
// Temps de réponse du serveur
|
|
const serverResponseTime = timing.responseEnd - timing.requestStart;
|
|
this.recordMetric('page.server_response_time', serverResponseTime);
|
|
|
|
// Temps de rendu DOM
|
|
const domRenderTime = timing.domContentLoadedEventEnd - timing.domLoading;
|
|
this.recordMetric('page.dom_render_time', domRenderTime);
|
|
|
|
// Type de navigation
|
|
const navigationType = navigation.type === 0 ? 'navigate' :
|
|
navigation.type === 1 ? 'reload' :
|
|
navigation.type === 2 ? 'back_forward' : 'unknown';
|
|
this.recordMetric('page.navigation_type', 1, {
|
|
type: navigationType
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Envoie les métriques au serveur
|
|
*/
|
|
private static async sendMetrics(): Promise<void> {
|
|
if (this.metrics.length === 0 && this.errors.length === 0 && this.userActions.length === 0) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const payload = {
|
|
metrics: [...this.metrics],
|
|
errors: [...this.errors],
|
|
userActions: [...this.userActions],
|
|
timestamp: Date.now(),
|
|
url: window.location.href,
|
|
userAgent: navigator.userAgent
|
|
};
|
|
|
|
// Envoyer au serveur (endpoint à implémenter)
|
|
await fetch('/api/monitoring', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(payload)
|
|
});
|
|
|
|
// Vider les buffers après envoi réussi
|
|
this.metrics = [];
|
|
this.errors = [];
|
|
this.userActions = [];
|
|
|
|
} catch (error) {
|
|
console.warn('Échec de l\'envoi des métriques:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtient les statistiques actuelles
|
|
*/
|
|
static getStats(): {
|
|
metricsCount: number;
|
|
errorsCount: number;
|
|
userActionsCount: number;
|
|
isEnabled: boolean;
|
|
} {
|
|
return {
|
|
metricsCount: this.metrics.length,
|
|
errorsCount: this.errors.length,
|
|
userActionsCount: this.userActions.length,
|
|
isEnabled: this.isEnabled
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Active ou désactive le monitoring
|
|
*/
|
|
static setEnabled(enabled: boolean): void {
|
|
this.isEnabled = enabled;
|
|
}
|
|
|
|
/**
|
|
* Vide tous les buffers
|
|
*/
|
|
static clear(): void {
|
|
this.metrics = [];
|
|
this.errors = [];
|
|
this.userActions = [];
|
|
}
|
|
}
|
|
|
|
// Initialisation automatique
|
|
if (typeof window !== 'undefined') {
|
|
MonitoringService.init();
|
|
}
|
|
|
|
// Hook React pour le monitoring des composants
|
|
export function useMonitoring(componentName: string) {
|
|
const recordAction = (action: string, metadata?: Record<string, any>) => {
|
|
MonitoringService.recordUserAction(action, componentName, metadata);
|
|
};
|
|
|
|
const measureAsync = async <T>(
|
|
name: string,
|
|
fn: () => Promise<T>
|
|
): Promise<T> => {
|
|
return MonitoringService.measureTime(`${componentName}.${name}`, fn);
|
|
};
|
|
|
|
return {
|
|
recordAction,
|
|
measureAsync
|
|
};
|
|
}
|