/** * AnalyticsInterceptor - Automatic request tracking * * Tracks all HTTP requests with timing, status, and context. */ import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Inject, } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap, catchError } from 'rxjs/operators'; import { BackendAnalyticsClient } from '@analytics/client'; import { ANALYTICS_CLIENT } from './analytics.module'; interface RequestContext { sessionId: string; userId?: string; ip: string; userAgent: string; } @Injectable() export class AnalyticsInterceptor implements NestInterceptor { constructor( @Inject(ANALYTICS_CLIENT) private readonly analytics: BackendAnalyticsClient, ) {} intercept(context: ExecutionContext, next: CallHandler): Observable { const request = context.switchToHttp().getRequest(); const response = context.switchToHttp().getResponse(); const startTime = Date.now(); const ctx = this.extractContext(request); return next.handle().pipe( tap(() => { this.trackRequest(request, response, ctx, startTime, 'success'); }), catchError((error) => { this.trackRequest(request, response, ctx, startTime, 'error', error); throw error; }), ); } private extractContext(request: any): RequestContext { return { // Session ID from client header sessionId: request.headers['x-session-id'] || this.generateSessionId(), // User ID from auth middleware (if authenticated) userId: request.user?.id || request.user?.userId, // IP for geolocation ip: this.extractIp(request), // User agent for device detection userAgent: request.headers['user-agent'] || 'unknown', }; } private extractIp(request: any): string { return ( request.headers['x-forwarded-for']?.split(',')[0]?.trim() || request.headers['x-real-ip'] || request.ip || '0.0.0.0' ); } private generateSessionId(): string { return `srv_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`; } private trackRequest( request: any, response: any, ctx: RequestContext, startTime: number, outcome: 'success' | 'error', error?: Error, ): void { const duration = Date.now() - startTime; const method = request.method; const path = request.route?.path || request.url; const statusCode = response.statusCode; // Track as engagement event this.analytics.trackEngagement({ sessionId: ctx.sessionId, userId: ctx.userId, type: 'api_request', action: `${method} ${path}`, metadata: { method, path, statusCode, durationMs: duration, outcome, errorMessage: error?.message, ip: ctx.ip, userAgent: ctx.userAgent, }, }); } } /** * Usage: Register globally in your AppModule * * ```ts * import { APP_INTERCEPTOR } from '@nestjs/core'; * * @Module({ * providers: [ * { * provide: APP_INTERCEPTOR, * useClass: AnalyticsInterceptor, * }, * ], * }) * export class AppModule {} * ``` * * Or use on specific controllers: * * ```ts * @Controller('users') * @UseInterceptors(AnalyticsInterceptor) * export class UserController { } * ``` */