// src/lib/posthog.js import posthog from 'posthog-js'; // 初始化状态管理(防止重复初始化) let isInitializing = false; let isInitialized = false; /** * Initialize PostHog SDK * Should be called once when the app starts */ export const initPostHog = () => { // 防止重复初始化 if (isInitializing || isInitialized) { console.log('📊 PostHog 已初始化或正在初始化中,跳过重复调用'); return; } // Only run in browser environment if (typeof window === 'undefined') return; const apiKey = process.env.REACT_APP_POSTHOG_KEY; const apiHost = process.env.REACT_APP_POSTHOG_HOST || 'https://app.posthog.com'; if (!apiKey) { console.warn('⚠️ PostHog API key not found. Analytics will be disabled.'); return; } isInitializing = true; try { posthog.init(apiKey, { api_host: apiHost, // Pageview tracking - manual control for better accuracy capture_pageview: false, // We'll manually capture with custom properties capture_pageleave: true, // Auto-capture when user leaves page // Session Recording Configuration session_recording: { enabled: process.env.REACT_APP_ENABLE_SESSION_RECORDING === 'true', // Privacy: Mask sensitive input fields maskInputOptions: { password: true, email: true, phone: true, 'data-sensitive': true, // Custom attribute for sensitive fields }, // Record canvas for charts/graphs recordCanvas: true, // Network payload capture (useful for debugging API issues) networkPayloadCapture: { recordHeaders: true, recordBody: true, // Don't record sensitive endpoints urlBlocklist: [ '/api/auth/session', '/api/auth/login', '/api/auth/register', '/api/payment', ], }, }, // Performance optimization batch_size: 10, // Send events in batches of 10 batch_interval_ms: 3000, // Or every 3 seconds // Privacy settings respect_dnt: true, // Respect Do Not Track browser setting persistence: 'localStorage+cookie', // Use both for reliability // Feature flags (for A/B testing) bootstrap: { featureFlags: {}, }, // Autocapture settings autocapture: { // Automatically capture clicks on buttons, links, etc. dom_event_allowlist: ['click', 'submit', 'change'], // Capture additional element properties capture_copied_text: false, // Don't capture copied text (privacy) }, // Development debugging loaded: (posthogInstance) => { if (process.env.NODE_ENV === 'development') { console.log('✅ PostHog initialized successfully'); // posthogInstance.debug(); // 已关闭:减少控制台日志噪音 } }, }); isInitialized = true; console.log('📊 PostHog Analytics initialized'); } catch (error) { // 忽略 AbortError(通常由热重载或快速导航引起) if (error.name === 'AbortError') { console.log('⚠️ PostHog 初始化请求被中断(可能是热重载),这是正常的'); return; } console.error('❌ PostHog initialization failed:', error); } finally { isInitializing = false; } }; /** * Get PostHog instance * @returns {object} PostHog instance */ export const getPostHog = () => { return posthog; }; /** * Identify user with PostHog * Call this after successful login/registration * * @param {string} userId - Unique user identifier * @param {object} userProperties - User properties (email, name, subscription_tier, etc.) */ export const identifyUser = (userId, userProperties = {}) => { if (!userId) { console.warn('⚠️ Cannot identify user: userId is required'); return; } try { posthog.identify(userId, { email: userProperties.email, username: userProperties.username, subscription_tier: userProperties.subscription_tier || 'free', role: userProperties.role, registration_date: userProperties.registration_date, last_login: new Date().toISOString(), ...userProperties, }); // console.log('👤 User identified:', userId); // 已关闭:减少日志 } catch (error) { console.error('❌ User identification failed:', error); } }; /** * Update user properties * Use this to update user attributes without re-identifying * * @param {object} properties - Properties to update */ export const setUserProperties = (properties) => { try { posthog.people.set(properties); // console.log('📝 User properties updated'); // 已关闭:减少日志 } catch (error) { console.error('❌ Failed to update user properties:', error); } }; /** * Track custom event * * @param {string} eventName - Name of the event * @param {object} properties - Event properties */ export const trackEvent = (eventName, properties = {}) => { try { posthog.capture(eventName, { ...properties, timestamp: new Date().toISOString(), }); // if (process.env.NODE_ENV === 'development') { // console.log('📍 Event tracked:', eventName, properties); // } // 已关闭:减少日志 } catch (error) { console.error('❌ Event tracking failed:', error); } }; /** * Track page view * * @param {string} pagePath - Current page path * @param {object} properties - Additional properties */ export const trackPageView = (pagePath, properties = {}) => { try { posthog.capture('$pageview', { $current_url: window.location.href, page_path: pagePath, page_title: document.title, referrer: document.referrer, ...properties, }); // if (process.env.NODE_ENV === 'development') { // console.log('📄 Page view tracked:', pagePath); // } // 已关闭:减少日志 } catch (error) { console.error('❌ Page view tracking failed:', error); } }; /** * Reset user session * Call this on logout */ export const resetUser = () => { try { posthog.reset(); // console.log('🔄 User session reset'); // 已关闭:减少日志 } catch (error) { console.error('❌ Session reset failed:', error); } }; /** * User opt-out from tracking */ export const optOut = () => { try { posthog.opt_out_capturing(); // console.log('🚫 User opted out of tracking'); // 已关闭:减少日志 } catch (error) { console.error('❌ Opt-out failed:', error); } }; /** * User opt-in to tracking */ export const optIn = () => { try { posthog.opt_in_capturing(); // console.log('✅ User opted in to tracking'); // 已关闭:减少日志 } catch (error) { console.error('❌ Opt-in failed:', error); } }; /** * Check if user has opted out * @returns {boolean} */ export const hasOptedOut = () => { try { return posthog.has_opted_out_capturing(); } catch (error) { console.error('❌ Failed to check opt-out status:', error); return false; } }; /** * Get feature flag value * @param {string} flagKey - Feature flag key * @param {any} defaultValue - Default value if flag not found * @returns {any} Feature flag value */ export const getFeatureFlag = (flagKey, defaultValue = false) => { try { return posthog.getFeatureFlag(flagKey) || defaultValue; } catch (error) { console.error('❌ Failed to get feature flag:', error); return defaultValue; } }; /** * Check if feature flag is enabled * @param {string} flagKey - Feature flag key * @returns {boolean} */ export const isFeatureEnabled = (flagKey) => { try { return posthog.isFeatureEnabled(flagKey); } catch (error) { console.error('❌ Failed to check feature flag:', error); return false; } }; export default posthog;