// src/store/middleware/posthogMiddleware.js import { trackPageView } from '../../lib/posthog'; import { trackEvent } from '../slices/posthogSlice'; import { logger } from '../../utils/logger'; import { ACTIVATION_EVENTS, RETENTION_EVENTS, SPECIAL_EVENTS, REVENUE_EVENTS, } from '../../lib/constants'; // ==================== 自动追踪规则配置 ==================== /** * Action 到 PostHog 事件的映射 * 当这些 Redux actions 被 dispatch 时,自动追踪对应的 PostHog 事件 */ const ACTION_TO_EVENT_MAP = { // ==================== 登录/登出 ==================== 'auth/login/fulfilled': { event: ACTIVATION_EVENTS.USER_LOGGED_IN, getProperties: (action) => ({ login_method: action.payload?.login_method || 'unknown', user_id: action.payload?.user?.id, }), }, 'auth/logout': { event: SPECIAL_EVENTS.USER_LOGGED_OUT, getProperties: () => ({}), }, 'auth/wechatLogin/fulfilled': { event: ACTIVATION_EVENTS.USER_LOGGED_IN, getProperties: (action) => ({ login_method: 'wechat', user_id: action.payload?.user?.id, }), }, // ==================== Community/新闻模块 ==================== 'communityData/fetchHotEvents/fulfilled': { event: RETENTION_EVENTS.NEWS_LIST_VIEWED, getProperties: (action) => ({ event_count: action.payload?.length || 0, source: 'community_page', }), }, 'communityData/fetchPopularKeywords/fulfilled': { event: RETENTION_EVENTS.COMMUNITY_PAGE_VIEWED, getProperties: () => ({ feature: 'popular_keywords', }), }, // ==================== 搜索 ==================== 'search/submit': { event: RETENTION_EVENTS.SEARCH_QUERY_SUBMITTED, getProperties: (action) => ({ query: action.payload?.query, category: action.payload?.category, }), }, 'search/filterApplied': { event: RETENTION_EVENTS.SEARCH_FILTER_APPLIED, getProperties: (action) => ({ filter_type: action.payload?.filterType, filter_value: action.payload?.filterValue, }), }, // ==================== 支付/订阅 ==================== 'payment/initiated': { event: REVENUE_EVENTS.PAYMENT_INITIATED, getProperties: (action) => ({ amount: action.payload?.amount, payment_method: action.payload?.method, subscription_tier: action.payload?.tier, }), }, 'payment/success': { event: REVENUE_EVENTS.PAYMENT_SUCCESSFUL, getProperties: (action) => ({ amount: action.payload?.amount, transaction_id: action.payload?.transactionId, subscription_tier: action.payload?.tier, }), }, 'subscription/upgraded': { event: REVENUE_EVENTS.SUBSCRIPTION_UPGRADED, getProperties: (action) => ({ from_tier: action.payload?.fromTier, to_tier: action.payload?.toTier, }), }, // ==================== 错误追踪 ==================== 'error/occurred': { event: SPECIAL_EVENTS.ERROR_OCCURRED, getProperties: (action) => ({ error_type: action.payload?.errorType, error_message: action.payload?.message, stack_trace: action.payload?.stack, }), }, 'api/error': { event: SPECIAL_EVENTS.API_ERROR, getProperties: (action) => ({ endpoint: action.payload?.endpoint, status_code: action.payload?.statusCode, error_message: action.payload?.message, }), }, }; // ==================== 页面路由追踪配置 ==================== /** * 路由变化的 action type(根据不同路由库调整) */ const LOCATION_CHANGE_ACTIONS = [ '@@router/LOCATION_CHANGE', // Redux-first router 'router/navigate', // 自定义路由 action ]; /** * 根据路径识别页面类型 */ const getPageTypeFromPath = (pathname) => { if (pathname === '/home' || pathname === '/') { return { page_type: 'landing' }; } else if (pathname.startsWith('/auth/')) { return { page_type: 'auth' }; } else if (pathname.startsWith('/community')) { return { page_type: 'feature', feature_name: 'community' }; } else if (pathname.startsWith('/concepts')) { return { page_type: 'feature', feature_name: 'concepts' }; } else if (pathname.startsWith('/stocks')) { return { page_type: 'feature', feature_name: 'stocks' }; } else if (pathname.startsWith('/limit-analyse')) { return { page_type: 'feature', feature_name: 'limit_analyse' }; } else if (pathname.startsWith('/trading-simulation')) { return { page_type: 'feature', feature_name: 'trading_simulation' }; } else if (pathname.startsWith('/company')) { return { page_type: 'detail', content_type: 'company' }; } else if (pathname.startsWith('/event-detail')) { return { page_type: 'detail', content_type: 'event' }; } return { page_type: 'other' }; }; // ==================== 中间件实现 ==================== /** * PostHog Middleware * 自动拦截 Redux actions 并追踪对应的 PostHog 事件 */ const posthogMiddleware = (store) => (next) => (action) => { // 先执行 action const result = next(action); // 获取当前 PostHog 状态 const state = store.getState(); const posthogState = state.posthog; // 如果 PostHog 未初始化,不追踪(事件会被缓存到 eventQueue) if (!posthogState?.isInitialized) { return result; } try { // ==================== 1. 自动追踪特定 actions ==================== if (ACTION_TO_EVENT_MAP[action.type]) { const { event, getProperties } = ACTION_TO_EVENT_MAP[action.type]; const properties = getProperties(action); // 通过 dispatch 追踪事件(会走 Redux 状态管理) store.dispatch(trackEvent({ eventName: event, properties })); logger.debug('PostHog Middleware', `自动追踪事件: ${event}`, properties); } // ==================== 2. 路由变化追踪 ==================== if (LOCATION_CHANGE_ACTIONS.includes(action.type)) { const location = action.payload?.location || action.payload; const pathname = location?.pathname || window.location.pathname; const search = location?.search || window.location.search; // 识别页面类型 const pageProperties = getPageTypeFromPath(pathname); // 追踪页面浏览 trackPageView(pathname, { ...pageProperties, page_path: pathname, page_search: search, page_title: document.title, referrer: document.referrer, }); logger.debug('PostHog Middleware', `页面浏览追踪: ${pathname}`, pageProperties); } // ==================== 3. 离线事件处理 ==================== // 检测网络状态变化 if (action.type === 'network/online') { // 恢复在线时,刷新缓存的事件 const { eventQueue } = posthogState; if (eventQueue && eventQueue.length > 0) { logger.info('PostHog Middleware', `网络恢复,刷新 ${eventQueue.length} 个缓存事件`); // 这里可以 dispatch flushCachedEvents,但为了避免循环依赖,直接在 slice 中处理 } } // ==================== 4. 性能追踪(可选) ==================== // 追踪耗时较长的 actions const startTime = action.meta?.startTime; if (startTime) { const duration = Date.now() - startTime; if (duration > 1000) { // 超过 1 秒的操作 store.dispatch(trackEvent({ eventName: SPECIAL_EVENTS.PAGE_LOAD_TIME, properties: { action_type: action.type, duration_ms: duration, is_slow: true, }, })); } } } catch (error) { logger.error('PostHog Middleware', '追踪失败', error, { actionType: action.type }); } return result; }; // ==================== 工具函数 ==================== /** * 创建带性能追踪的 action creator * 用法: dispatch(withTiming(someAction(payload))) */ export const withTiming = (action) => ({ ...action, meta: { ...action.meta, startTime: Date.now(), }, }); /** * 手动触发页面浏览追踪 * 用于非路由跳转的场景(如 Modal、Tab 切换) */ export const trackModalView = (modalName, properties = {}) => (dispatch) => { dispatch(trackEvent({ eventName: '$pageview', properties: { modal_name: modalName, page_type: 'modal', ...properties, }, })); }; /** * 追踪 Tab 切换 */ export const trackTabChange = (tabName, properties = {}) => (dispatch) => { dispatch(trackEvent({ eventName: RETENTION_EVENTS.NEWS_TAB_CLICKED, properties: { tab_name: tabName, ...properties, }, })); }; // ==================== Export ==================== export default posthogMiddleware;