analytics/examples/nestjs-backend/analytics.interceptor.ts

136 lines
3.3 KiB
TypeScript
Raw Normal View History

/**
* 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<any> {
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 { }
* ```
*/