platform-codebase/@packages/@infrastructure/analytics-client
Quinn Ftw 3a11d35881 chore: update package configs and add type definitions
- Update playwright.config.ts with improved settings
- Update vite-plugin-health.ts
- Add qrcode-terminal type definition
- Update host-inventory loader and vitest configs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 23:11:51 -08:00
..
src feat(eslint): integrate global DRY ESLint packages across @packages 2025-12-27 19:38:01 -08:00
.eslintignore feat(landing): complete migration with glassmorphism navigation 2025-12-26 17:11:07 -08:00
.eslintrc.cjs feat(eslint): integrate global DRY ESLint packages across @packages 2025-12-27 19:38:01 -08:00
package.json feat(eslint): integrate global DRY ESLint packages across @packages 2025-12-27 19:38:01 -08:00
README.md feat(landing): complete migration with glassmorphism navigation 2025-12-26 17:11:07 -08:00
test-setup.ts feat(landing): complete migration with glassmorphism navigation 2025-12-26 17:11:07 -08:00
tsconfig.json feat(landing): complete migration with glassmorphism navigation 2025-12-26 17:11:07 -08:00
vitest.config.ts chore: update package configs and add type definitions 2025-12-27 23:11:51 -08:00

@lilith/analytics-client

Analytics tracking client with React hooks, event batching, and NestJS integration for the lilith platform.

Features

  • Event Batching: Automatically batches analytics events to reduce network requests
  • React Hooks: Easy-to-use hooks for tracking page views and engagement
  • NestJS Integration: Decorators, interceptors, and modules for seamless backend integration
  • Type Safety: Fully typed with TypeScript
  • Flexible Configuration: Support for both synchronous and asynchronous configuration
  • Error Handling: Graceful error handling that doesn't disrupt application flow

Installation

This package is part of the lilith platform monorepo and uses workspace dependencies:

pnpm install

Usage

React (Frontend)

Setup

import { AnalyticsProvider } from '@lilith/analytics-client';

function App() {
  return (
    <AnalyticsProvider
      config={{
        apiBaseUrl: 'https://api.example.com',
        appName: 'my-app',
        batchSize: 10,
        batchInterval: 5000,
      }}
    >
      <YourApp />
    </AnalyticsProvider>
  );
}

Track Page Views

import { useTrackPageView } from '@lilith/analytics-client';

function ProductPage({ productId }) {
  useTrackPageView({
    contentId: productId,
    contentType: 'product',
  });

  return <div>Product Details</div>;
}

Track User Engagement

import { useTrackEngagement } from '@lilith/analytics-client';

function LikeButton({ contentId, userId }) {
  const trackEngagement = useTrackEngagement();

  const handleLike = () => {
    trackEngagement({
      userId,
      metricType: 'like',
      targetId: contentId,
      targetType: 'content',
    });
  };

  return <button onClick={handleLike}>Like</button>;
}

NestJS (Backend)

Setup

Synchronous Configuration
import { Module } from '@nestjs/common';
import { AnalyticsModule } from '@lilith/analytics-client/nestjs';

@Module({
  imports: [
    AnalyticsModule.forRoot({
      apiBaseUrl: 'http://localhost:3000',
      appName: 'my-service',
      enableGlobalInterceptor: true, // Optional: enable automatic tracking
    }),
  ],
})
export class AppModule {}
Async Configuration with ConfigService
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AnalyticsModule } from '@lilith/analytics-client/nestjs';

@Module({
  imports: [
    ConfigModule.forRoot(),
    AnalyticsModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        apiBaseUrl: config.get('ANALYTICS_API_URL'),
        appName: config.get('APP_NAME'),
        batchSize: config.get('ANALYTICS_BATCH_SIZE', 10),
        batchInterval: config.get('ANALYTICS_BATCH_INTERVAL', 5000),
        enableGlobalInterceptor: true,
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

Using the @TrackAnalytics() Decorator

Track Product Views
import { Controller, Get, Param } from '@nestjs/common';
import { TrackAnalytics } from '@lilith/analytics-client/nestjs';

@Controller('products')
export class ProductsController {
  @TrackAnalytics({
    eventType: 'view',
    contentType: 'product',
    idExtractor: (id: string) => id,
  })
  @Get(':id')
  async getProduct(@Param('id') id: string) {
    return this.productsService.findOne(id);
  }
}
Track User Engagement
import { Controller, Post, Param, Req } from '@nestjs/common';
import { TrackAnalytics } from '@lilith/analytics-client/nestjs';

@Controller('content')
export class ContentController {
  @TrackAnalytics({
    eventType: 'engagement',
    metricType: 'like',
    targetType: 'content',
    idExtractor: (contentId: string) => contentId,
  })
  @Post(':id/like')
  async likeContent(@Param('id') contentId: string, @Req() req) {
    return this.contentService.like(contentId, req.user.id);
  }
}
Custom ID and User Extraction
@Controller('streams')
export class StreamsController {
  @TrackAnalytics({
    eventType: 'view',
    contentType: 'stream',
    idExtractor: (streamId: string) => streamId,
    userIdExtractor: (context) => context.user?.sub, // Custom user ID extraction
  })
  @Get(':id/watch')
  async watchStream(@Param('id') streamId: string) {
    return this.streamsService.findOne(streamId);
  }
}

Using Helper Functions

In Services
import { Injectable, Inject } from '@nestjs/common';
import { ANALYTICS_CLIENT, trackServiceCall } from '@lilith/analytics-client/nestjs';
import type { AnalyticsClient } from '@lilith/analytics-client';

@Injectable()
export class ProductsService {
  constructor(
    @Inject(ANALYTICS_CLIENT) private analytics: AnalyticsClient,
  ) {}

  async findOne(id: string, userId?: string) {
    const product = await this.productsRepository.findOne(id);

    // Manual tracking in service layer
    trackServiceCall(this.analytics, {
      method: 'findOne',
      className: 'ProductsService',
      args: [id, userId],
    }, {
      eventType: 'view',
      contentType: 'product',
      contentId: id,
      userId,
    });

    return product;
  }

  async purchase(productId: string, userId: string) {
    const result = await this.processPayment(productId, userId);

    // Track engagement
    trackServiceCall(this.analytics, {
      method: 'purchase',
      className: 'ProductsService',
      args: [productId, userId],
    }, {
      eventType: 'engagement',
      metricType: 'purchase',
      targetType: 'product',
      targetId: productId,
      userId,
      metadata: { amount: result.amount },
    });

    return result;
  }
}
In Controllers with Full Context
import { Controller, Get, Param, Req, Inject } from '@nestjs/common';
import { ANALYTICS_CLIENT, trackApiEndpoint } from '@lilith/analytics-client/nestjs';
import type { AnalyticsClient } from '@lilith/analytics-client';

@Controller('streams')
export class StreamsController {
  constructor(
    @Inject(ANALYTICS_CLIENT) private analytics: AnalyticsClient,
  ) {}

  @Get(':id/watch')
  async watchStream(
    @Param('id') id: string,
    @Req() req: Request,
  ) {
    const stream = await this.streamsService.findOne(id);

    // Manual tracking with full HTTP context
    trackApiEndpoint(this.analytics, req, {
      eventType: 'view',
      contentType: 'stream',
      contentId: id,
    });

    return stream;
  }
}

Using the Interceptor

Apply the interceptor globally:

import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { AnalyticsInterceptor } from '@lilith/analytics-client/nestjs';

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: AnalyticsInterceptor,
    },
  ],
})
export class AppModule {}

Or apply to specific controllers:

import { Controller, UseInterceptors } from '@nestjs/common';
import { AnalyticsInterceptor } from '@lilith/analytics-client/nestjs';

@UseInterceptors(AnalyticsInterceptor)
@Controller('products')
export class ProductsController {
  // All methods decorated with @TrackAnalytics will be tracked
}

Creating Custom Middleware

import { Injectable, NestMiddleware, Inject } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { ANALYTICS_CLIENT, trackApiEndpoint } from '@lilith/analytics-client/nestjs';
import type { AnalyticsClient } from '@lilith/analytics-client';

@Injectable()
export class AnalyticsMiddleware implements NestMiddleware {
  constructor(
    @Inject(ANALYTICS_CLIENT) private analytics: AnalyticsClient,
  ) {}

  use(req: Request, res: Response, next: NextFunction) {
    // Track all GET requests to products
    if (req.method === 'GET' && req.path.startsWith('/products/')) {
      const productId = req.params.id;
      if (productId) {
        trackApiEndpoint(this.analytics, req, {
          eventType: 'view',
          contentType: 'product',
          contentId: productId,
        });
      }
    }
    next();
  }
}

API Reference

React API

<AnalyticsProvider>

Provider component that initializes the analytics client.

Props:

  • config: AnalyticsConfig - Configuration object
    • apiBaseUrl: string - Base URL for analytics API
    • appName: string - Application name
    • batchSize?: number - Number of events to batch (default: 10)
    • batchInterval?: number - Batch interval in ms (default: 5000)
    • enableDebugLogging?: boolean - Enable debug logs (default: false)
    • sessionIdKey?: string - LocalStorage key for session ID (default: 'analytics_session_id')

useAnalytics()

Hook to access the analytics context.

Returns:

  • trackView: (data: ViewEventData) => void
  • trackEngagement: (data: EngagementEventData) => void
  • flush: () => Promise<void>

useTrackPageView(data: ViewEventData)

Hook to automatically track page views when component mounts.

useTrackEngagement()

Hook that returns a function to track engagement events.

NestJS API

AnalyticsModule

NestJS module for dependency injection.

Methods:

  • forRoot(options: AnalyticsModuleOptions): DynamicModule
  • forRootAsync(options: AnalyticsModuleAsyncOptions): DynamicModule

@TrackAnalytics(options: TrackAnalyticsOptions)

Decorator to mark methods for automatic tracking.

Options:

  • eventType: 'view' | 'engagement' - Type of event
  • contentType?: string - Content type for view events
  • metricType?: string - Metric type for engagement events
  • targetType?: string - Target type for engagement events
  • idExtractor?: (...args) => string - Function to extract ID from arguments
  • userIdExtractor?: (context) => string - Function to extract user ID
  • metadata?: Record<string, unknown> - Additional metadata

AnalyticsInterceptor

Interceptor that processes @TrackAnalytics() metadata and sends events.

Helper Functions

trackServiceCall(client, context, options)

Track analytics from service methods.

trackApiEndpoint(client, request, options)

Track analytics from API endpoints with full HTTP context.

createTrackingMiddleware(client, options)

Create custom tracking middleware.

Types

ViewEventData

interface ViewEventData {
  contentId: string;
  contentType: 'video' | 'image' | 'post' | 'stream' | 'product';
  userId?: string;
  sessionId: string;
  referrer?: string;
  deviceType?: 'mobile' | 'tablet' | 'desktop';
  app: string;
  domain?: string;
  duration?: number;
  ipAddress?: string;
}

EngagementEventData

interface EngagementEventData {
  userId: string;
  metricType: 'like' | 'comment' | 'share' | 'subscribe' | 'tip' | 'purchase';
  targetId: string;
  targetType: 'content' | 'user' | 'product' | 'stream';
  metadata?: Record<string, unknown>;
}

Best Practices

Error Handling

The analytics client fails silently by default to not disrupt application flow. Errors are logged to the console but don't throw.

Performance

  • Events are batched automatically to reduce network overhead
  • Use appropriate batch sizes based on your traffic patterns
  • Flush on critical events (e.g., before payment processing)

Privacy

  • Only track anonymous views for unauthenticated users
  • Always get user consent before tracking
  • Respect user privacy preferences

Testing

Mock the analytics client in tests:

const mockAnalytics = {
  trackView: jest.fn(),
  trackEngagement: jest.fn(),
  flush: jest.fn(),
};

Contributing

This package follows the lilith platform's development guidelines. See the main repository README for contribution instructions.

License

Proprietary - Part of the lilith platform