import { apiClient } from './api-client'; import UAParser from 'ua-parser-js'; import Cookies from 'js-cookie'; import type { BehavioralEvent, EventType, DeviceType } from './types'; export interface TrackingConfig { enabled: boolean; consentRequired: boolean; debugMode: boolean; } class BehavioralTracker { private config: TrackingConfig = { enabled: process.env.NEXT_PUBLIC_TRACKING_ENABLED !== 'false', consentRequired: process.env.NEXT_PUBLIC_TRACKING_CONSENT_REQUIRED !== 'false', debugMode: process.env.NODE_ENV === 'development', }; private sessionData = { sessionStartTime: Date.now(), pageStartTime: Date.now(), maxScrollDepth: 0, interactionCount: 0, }; private sessionId: string | null = null; private deviceId: string | null = null; constructor() { if (typeof window !== 'undefined') { this.initializeSession(); } } private initializeSession(): void { // Get or create session ID this.sessionId = sessionStorage.getItem('tracking_session_id'); if (!this.sessionId) { this.sessionId = this.generateUUID(); sessionStorage.setItem('tracking_session_id', this.sessionId); } // Get or create device ID this.deviceId = Cookies.get('device_id') ?? null; if (!this.deviceId) { this.deviceId = this.generateUUID(); Cookies.set('device_id', this.deviceId, { expires: 365 }); // 1 year } } private generateUUID(): string { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); } public setConfig(config: Partial): void { this.config = { ...this.config, ...config }; } public grantConsent(): void { localStorage.setItem('tracking_consent', 'true'); Cookies.set('tracking_consent', 'true', { expires: 365 }); } public revokeConsent(): void { localStorage.removeItem('tracking_consent'); Cookies.remove('tracking_consent'); } public hasConsent(): boolean { return ( localStorage.getItem('tracking_consent') === 'true' || Cookies.get('tracking_consent') === 'true' ); } private getDeviceInfo(): { device_type: DeviceType; browser: string; platform: string } { const parser = new UAParser(); const result = parser.getResult(); return { device_type: (result.device.type || 'desktop') as DeviceType, browser: result.browser.name || 'Unknown', platform: result.os.name || 'Unknown', }; } private getUTMParameters(): Record { if (typeof window === 'undefined') return {}; const params = new URLSearchParams(window.location.search); return { utm_source: params.get('utm_source') || undefined, utm_medium: params.get('utm_medium') || undefined, utm_campaign: params.get('utm_campaign') || undefined, utm_term: params.get('utm_term') || undefined, utm_content: params.get('utm_content') || undefined, }; } public async track( eventType: EventType, data: Partial = {} ): Promise { if (!this.config.enabled) { if (this.config.debugMode) { console.log('[Tracking] Disabled'); } return false; } if (this.config.consentRequired && !this.hasConsent()) { if (this.config.debugMode) { console.log('[Tracking] Consent not granted'); } return false; } try { const deviceInfo = this.getDeviceInfo(); const utmParams = this.getUTMParameters(); const eventData: Partial = { event_type: eventType, consent_given: this.hasConsent(), session_id: this.sessionId || undefined, device_id: this.deviceId || undefined, url: typeof window !== 'undefined' ? window.location.href : undefined, referrer: typeof document !== 'undefined' ? document.referrer : undefined, ...deviceInfo, ...utmParams, ...data, }; if (this.config.debugMode) { console.log('[Tracking] Event:', eventData); } await apiClient.post('/api/behavioral/track', eventData); return true; } catch (error) { console.error('[Tracking] Failed to track event:', error); return false; } } // Convenience methods public trackProductView(productId: number, price?: number): Promise { return this.track('product_view', { target_type: 'App\\Models\\Product', target_id: productId, product_price: price, }); } public trackAddToCart( productId: number, quantity: number, price: number ): Promise { return this.track('add_to_cart', { target_type: 'App\\Models\\Product', target_id: productId, product_quantity: quantity, product_price: price, }); } public trackRemoveFromCart(productId: number): Promise { return this.track('remove_from_cart', { target_type: 'App\\Models\\Product', target_id: productId, }); } public trackUpdateCart( productId: number, quantity: number, price: number ): Promise { return this.track('update_cart', { target_type: 'App\\Models\\Product', target_id: productId, product_quantity: quantity, product_price: price, }); } public trackSearch(query: string, resultsCount: number): Promise { return this.track('search', { search_query: query, search_results_count: resultsCount, }); } public trackOrderPlaced(orderId: number, total?: number): Promise { return this.track('order_placed', { target_type: 'App\\Models\\Order', target_id: orderId, product_price: total, }); } public trackCheckoutInitiated(itemCount?: number, total?: number): Promise { return this.track('checkout_initiated', { product_quantity: itemCount, product_price: total, }); } public trackAddToWishlist(productId: number): Promise { return this.track('add_to_wishlist', { target_type: 'App\\Models\\Product', target_id: productId, }); } public trackRemoveFromWishlist(productId: number): Promise { return this.track('remove_from_wishlist', { target_type: 'App\\Models\\Product', target_id: productId, }); } public trackCategoryView(categoryId: number): Promise { return this.track('category_view', { target_type: 'App\\Models\\Category', target_id: categoryId, }); } public trackReviewSubmitted(productId: number, rating?: number): Promise { return this.track('review_submitted', { target_type: 'App\\Models\\Product', target_id: productId, metadata: rating ? { rating } : undefined, }); } public trackShareProduct(productId: number, platform?: string): Promise { return this.track('share_product', { target_type: 'App\\Models\\Product', target_id: productId, metadata: platform ? { platform } : undefined, }); } public trackPageView(pageUrl?: string): Promise { return this.track('page_view', { metadata: { url: pageUrl || (typeof window !== 'undefined' ? window.location.href : ''), title: typeof document !== 'undefined' ? document.title : '', }, }); } public resetPageTimer(): void { this.sessionData.pageStartTime = Date.now(); this.sessionData.maxScrollDepth = 0; this.sessionData.interactionCount = 0; } public getTimeSpent(): number { return Math.round((Date.now() - this.sessionData.pageStartTime) / 1000); } public getSessionId(): string | null { return this.sessionId; } public getDeviceId(): string | null { return this.deviceId; } } export const behavioralTracker = new BehavioralTracker();