135 lines
3.3 KiB
TypeScript
135 lines
3.3 KiB
TypeScript
/**
|
|
* 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 { }
|
|
* ```
|
|
*/
|