/** * Metric Service для Яндекс Метрики и Google Analytics * * Паттерн идентичен aura-webapp/src/services/metric/metricService.ts * * Основные методы: * - setUserID: установить ID пользователя (для YM и GA) * - userParams: отправить параметры ПОСЕТИТЕЛЯ (sessionId, email, age, gender и т.д.) * - params: отправить параметры ВИЗИТА (AB тест варианты, источник и т.д.) * - reachGoal: достижение цели */ /** * Параметры посетителя (постоянные характеристики) * Передаются через window.ym(counterId, "userParams", {...}) * * Эти данные привязываются к ClientID и распространяются * на всю историю визитов пользователя */ interface IUserParams { UserID?: string | number; sessionId?: string; email?: string; gender?: string; age?: number; partnerGender?: string; partnerAge?: number; } /** * Параметры визита (временные данные о визите) * Передаются через window.ym(counterId, "params", {...}) * * Эти данные привязываются к конкретному визиту */ interface IVisitParams { email?: string; userId?: string | number; sessionId?: string; gaId?: string; // Google Analytics Measurement ID (e.g., G-XXXXXXXXXX) gaClientId?: string; // Google Analytics Client ID from _ga cookie ymClientId?: string; // Yandex Metrika Client ID from _ym_uid cookie funnelId?: string; selectedProductId?: string; [key: string]: string | number | boolean | undefined; } const checkIsAvailableYandexMetric = (): boolean => { if (typeof window === 'undefined') return false; if (typeof window.ym === 'undefined' || !window.__YM_COUNTER_ID__) { console.warn('[MetricService] Yandex Metrika not initialized'); return false; } return true; }; const checkIsAvailableGoogleAnalytics = (): boolean => { if (typeof window === 'undefined') return false; if (typeof window.gtag === 'undefined') { console.warn('[MetricService] Google Analytics not initialized'); return false; } return true; }; /** * Установить ID пользователя * Вызывается после авторизации */ const setUserID = (userId: string | number): void => { console.log('[MetricService] setUserID:', userId); // Yandex Metrika if (checkIsAvailableYandexMetric() && window.__YM_COUNTER_ID__) { window.ym(window.__YM_COUNTER_ID__, 'setUserID', String(userId)); console.log('✅ [Yandex Metrika] setUserID:', userId); } // Google Analytics if (checkIsAvailableGoogleAnalytics()) { window.gtag('config', 'GA_MEASUREMENT_ID', { user_id: String(userId), }); console.log('✅ [Google Analytics] setUserID:', userId); } }; /** * Отправить параметры ПОСЕТИТЕЛЯ * * Эти параметры привязываются к ClientID и распространяются * на всю историю визитов пользователя * * Примеры: * - userParams({ sessionId: 'abc123' }) - при создании сессии * - userParams({ email: 'user@example.com', UserID: 123 }) - после авторизации * - userParams({ gender: 'female', age: 25 }) - после ввода данных */ const userParams = (parameters: Partial): void => { console.log('[MetricService] userParams:', parameters); // Yandex Metrika if (checkIsAvailableYandexMetric() && window.__YM_COUNTER_ID__) { window.ym(window.__YM_COUNTER_ID__, 'userParams', parameters); console.log('✅ [Yandex Metrika] userParams sent:', parameters); } // Google Analytics if (checkIsAvailableGoogleAnalytics()) { window.gtag('config', 'GA_MEASUREMENT_ID', { send_page_view: false, ...parameters, }); console.log('✅ [Google Analytics] config sent:', parameters); } }; /** * Отправить параметры ВИЗИТА * * Эти параметры привязываются к конкретному визиту * * Примеры: * - params({ ab_test_button: 'v1' }) - AB тест вариант * - params({ source: 'facebook' }) - источник трафика */ const params = (parameters: IVisitParams): void => { console.log('[MetricService] params:', parameters); // Yandex Metrika if (checkIsAvailableYandexMetric() && window.__YM_COUNTER_ID__) { window.ym(window.__YM_COUNTER_ID__, 'params', parameters); console.log('✅ [Yandex Metrika] params sent:', parameters); } }; /** * Достижение цели */ const reachGoal = (goal: string, params?: Record): void => { console.log('[MetricService] reachGoal:', goal, params); // Yandex Metrika if (checkIsAvailableYandexMetric() && window.__YM_COUNTER_ID__) { window.ym(window.__YM_COUNTER_ID__, 'reachGoal', goal, params); console.log('✅ [Yandex Metrika] reachGoal sent:', goal); } // Google Analytics if (checkIsAvailableGoogleAnalytics()) { window.gtag('event', goal, params); console.log('✅ [Google Analytics] event sent:', goal); } }; /** * Отправляет данные из sessionData в userParams * Использует те же данные что собираются через registrationFieldKey * Дополнительно вычисляет age из birthdate полей */ const sendSessionDataToMetrics = (sessionData: Record): void => { if (Object.keys(sessionData).length === 0) return; const metrics: Record = {}; // Рекурсивная функция для извлечения всех полей const extractFields = (obj: Record, prefix = ''): void => { Object.entries(obj).forEach(([key, value]) => { const fieldKey = prefix ? `${prefix}.${key}` : key; if (value && typeof value === 'object' && !Array.isArray(value)) { // Рекурсивно обрабатываем вложенные объекты extractFields(value as Record, fieldKey); } else if (typeof value === 'string' || typeof value === 'number') { // Для birthdate полей вычисляем age if (key === 'birthdate' && typeof value === 'string') { const age = calculateAge(value); if (age) { const ageKey = prefix ? `${prefix.split('.').pop()}Age` : 'age'; metrics[ageKey] = age; } } // Добавляем все остальные поля как есть metrics[key] = value; } }); }; extractFields(sessionData); // Отправляем только если есть данные if (Object.keys(metrics).length > 0) { userParams(metrics as Partial); } }; /** * Вычисляет возраст из даты рождения * @param birthdate - дата в формате YYYY-MM-DD HH:mm */ const calculateAge = (birthdate: string): number | undefined => { try { const date = new Date(birthdate); if (isNaN(date.getTime())) return undefined; const today = new Date(); let age = today.getFullYear() - date.getFullYear(); const monthDiff = today.getMonth() - date.getMonth(); if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < date.getDate())) { age--; } return age > 0 && age < 150 ? age : undefined; } catch { return undefined; } }; /** * Извлекает Google Analytics Client ID из куки _ga * Формат куки: GA1.1.XXXXXXXXXX.YYYYYYYYYY * Возвращает: XXXXXXXXXX.YYYYYYYYYY */ const getGoogleAnalyticsClientId = (): string | undefined => { if (typeof window === 'undefined' || typeof document === 'undefined') { return undefined; } try { const gaCookie = document.cookie .split('; ') .find(row => row.startsWith('_ga=')); if (!gaCookie) return undefined; const cookieValue = gaCookie.split('=')[1]; // Формат: GA1.1.XXXXXXXXXX.YYYYYYYYYY // Извлекаем последние две части const parts = cookieValue.split('.'); if (parts.length >= 3) { return parts.slice(2).join('.'); } return undefined; } catch { return undefined; } }; /** * Извлекает Yandex Metrika Client ID из куки _ym_uid * Возвращает значение напрямую */ const getYandexMetrikaClientId = (): string | undefined => { if (typeof window === 'undefined' || typeof document === 'undefined') { return undefined; } try { const ymCookie = document.cookie .split('; ') .find(row => row.startsWith('_ym_uid=')); if (!ymCookie) return undefined; return ymCookie.split('=')[1]; } catch { return undefined; } }; /** * Отправляет контекст визита в Яндекс Метрику * Это параметры визита (params), а не посетителя (userParams) * * Вызывается при: * - Создании сессии (sessionId, funnelId, gaId, gaClientId) * - Авторизации (email, userId) * - Выборе продукта на экране оплаты (selectedProductId) */ const sendVisitContext = (context: Partial): void => { // Автоматически добавляем GA Client ID и YM Client ID если их нет в контексте const gaClientId = context.gaClientId || getGoogleAnalyticsClientId(); const ymClientId = context.ymClientId || getYandexMetrikaClientId(); const contextWithClientIds = { ...context, ...(gaClientId ? { gaClientId } : {}), ...(ymClientId ? { ymClientId } : {}), }; console.log('[MetricService] sendVisitContext:', contextWithClientIds); // Фильтруем undefined значения const filteredContext = Object.entries(contextWithClientIds).reduce((acc, [key, value]) => { if (value !== undefined) { acc[key] = value; } return acc; }, {} as IVisitParams); if (Object.keys(filteredContext).length > 0) { params(filteredContext); } }; export const metricService = { setUserID, userParams, params, reachGoal, sendSessionDataToMetrics, sendVisitContext, };