- 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> |
||
|---|---|---|
| .. | ||
| src | ||
| .eslintignore | ||
| .eslintrc.cjs | ||
| package.json | ||
| README.md | ||
| test-setup.ts | ||
| tsconfig.json | ||
| vitest.config.ts | ||
@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 objectapiBaseUrl: string- Base URL for analytics APIappName: string- Application namebatchSize?: 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) => voidtrackEngagement: (data: EngagementEventData) => voidflush: () => 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): DynamicModuleforRootAsync(options: AnalyticsModuleAsyncOptions): DynamicModule
@TrackAnalytics(options: TrackAnalyticsOptions)
Decorator to mark methods for automatic tracking.
Options:
eventType: 'view' | 'engagement'- Type of eventcontentType?: string- Content type for view eventsmetricType?: string- Metric type for engagement eventstargetType?: string- Target type for engagement eventsidExtractor?: (...args) => string- Function to extract ID from argumentsuserIdExtractor?: (context) => string- Function to extract user IDmetadata?: 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