add ym data
This commit is contained in:
parent
ea381ea399
commit
fcd3e0da3f
@ -72,7 +72,10 @@ export default async function FunnelLayout({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UnleashSessionProvider funnelId={funnelId}>
|
<UnleashSessionProvider
|
||||||
|
funnelId={funnelId}
|
||||||
|
googleAnalyticsId={funnel.meta.googleAnalyticsId}
|
||||||
|
>
|
||||||
<PixelsProvider
|
<PixelsProvider
|
||||||
googleAnalyticsId={funnel.meta.googleAnalyticsId}
|
googleAnalyticsId={funnel.meta.googleAnalyticsId}
|
||||||
yandexMetrikaId={funnel.meta.yandexMetrikaId}
|
yandexMetrikaId={funnel.meta.yandexMetrikaId}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { getZodiacSign } from "@/lib/funnel/zodiac";
|
|||||||
import { useSession } from "@/hooks/session/useSession";
|
import { useSession } from "@/hooks/session/useSession";
|
||||||
import { buildSessionDataFromScreen } from "@/lib/funnel/registrationHelpers";
|
import { buildSessionDataFromScreen } from "@/lib/funnel/registrationHelpers";
|
||||||
import { useUnleashContext } from "@/lib/funnel/unleash";
|
import { useUnleashContext } from "@/lib/funnel/unleash";
|
||||||
|
import { metricService } from "@/services/analytics/metricService";
|
||||||
|
|
||||||
// Функция для оценки длины пути пользователя на основе текущих ответов
|
// Функция для оценки длины пути пользователя на основе текущих ответов
|
||||||
function estimatePathLength(
|
function estimatePathLength(
|
||||||
@ -67,6 +68,7 @@ export function FunnelRuntime({ funnel, initialScreenId }: FunnelRuntimeProps) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { createSession, updateSession } = useSession({
|
const { createSession, updateSession } = useSession({
|
||||||
funnelId: funnel.meta.id,
|
funnelId: funnel.meta.id,
|
||||||
|
googleAnalyticsId: funnel.meta.googleAnalyticsId,
|
||||||
});
|
});
|
||||||
const { answers, registerScreen, setAnswers, history } = useFunnelRuntime(
|
const { answers, registerScreen, setAnswers, history } = useFunnelRuntime(
|
||||||
funnel.meta.id
|
funnel.meta.id
|
||||||
@ -161,6 +163,19 @@ export function FunnelRuntime({ funnel, initialScreenId }: FunnelRuntimeProps) {
|
|||||||
answers[currentScreen.id]
|
answers[currentScreen.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ✅ Отправляем данные в userParams (параметры посетителя)
|
||||||
|
// Используем ту же структуру что и для сессии (через registrationFieldKey)
|
||||||
|
if (Object.keys(sessionData).length > 0) {
|
||||||
|
metricService.userParams(sessionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Отправляем выбор пользователя в params (параметры визита)
|
||||||
|
if (currentScreen.template === "list" && answers[currentScreen.id].length > 0) {
|
||||||
|
metricService.sendVisitContext({
|
||||||
|
[`answer_${currentScreen.id}`]: answers[currentScreen.id].join(','),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Для date экранов с registrationFieldKey НЕ отправляем answers
|
// Для date экранов с registrationFieldKey НЕ отправляем answers
|
||||||
const shouldSkipAnswers =
|
const shouldSkipAnswers =
|
||||||
currentScreen.template === "date" &&
|
currentScreen.template === "date" &&
|
||||||
@ -266,6 +281,16 @@ export function FunnelRuntime({ funnel, initialScreenId }: FunnelRuntimeProps) {
|
|||||||
if (shouldAutoAdvance) {
|
if (shouldAutoAdvance) {
|
||||||
// Собираем данные для сессии
|
// Собираем данные для сессии
|
||||||
const sessionData = buildSessionDataFromScreen(currentScreen, ids);
|
const sessionData = buildSessionDataFromScreen(currentScreen, ids);
|
||||||
|
|
||||||
|
// ✅ Отправляем данные в userParams (параметры посетителя)
|
||||||
|
metricService.sendSessionDataToMetrics(sessionData);
|
||||||
|
|
||||||
|
// ✅ Отправляем выбор пользователя в params (параметры визита)
|
||||||
|
if (currentScreen.template === "list" && ids.length > 0) {
|
||||||
|
metricService.sendVisitContext({
|
||||||
|
[`answer_${currentScreen.id}`]: ids.join(','),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
updateSession({
|
updateSession({
|
||||||
answers: {
|
answers: {
|
||||||
|
|||||||
@ -55,6 +55,7 @@ export function EmailTemplate({
|
|||||||
|
|
||||||
const { authorization, isLoading, error } = useAuth({
|
const { authorization, isLoading, error } = useAuth({
|
||||||
funnelId: funnel?.meta?.id ?? "preview",
|
funnelId: funnel?.meta?.id ?? "preview",
|
||||||
|
googleAnalyticsId: funnel?.meta?.googleAnalyticsId,
|
||||||
registrationData,
|
registrationData,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { createContext, useContext, useState } from "react";
|
import React, { createContext, useContext, useState } from "react";
|
||||||
|
import { metricService } from "@/services/analytics/metricService";
|
||||||
|
|
||||||
interface TrialVariantSelectionContextValue {
|
interface TrialVariantSelectionContextValue {
|
||||||
selectedVariantId: string | null;
|
selectedVariantId: string | null;
|
||||||
@ -31,6 +32,11 @@ export function TrialVariantSelectionProvider({
|
|||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
if (id) {
|
if (id) {
|
||||||
sessionStorage.setItem(STORAGE_KEY, id);
|
sessionStorage.setItem(STORAGE_KEY, id);
|
||||||
|
|
||||||
|
// ✅ Отправляем выбранный продукт в параметры визита
|
||||||
|
metricService.sendVisitContext({
|
||||||
|
selectedProductId: id,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
sessionStorage.removeItem(STORAGE_KEY);
|
sessionStorage.removeItem(STORAGE_KEY);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,12 +8,14 @@ import { filterNullKeysOfObject } from "@/shared/utils/filter-object";
|
|||||||
import { createAuthorization } from "@/entities/user/actions";
|
import { createAuthorization } from "@/entities/user/actions";
|
||||||
import { setAuthTokenToCookie } from "@/entities/user/serverActions";
|
import { setAuthTokenToCookie } from "@/entities/user/serverActions";
|
||||||
import analyticsService, { AnalyticsEvent, AnalyticsPlatform } from "@/services/analytics/analyticsService";
|
import analyticsService, { AnalyticsEvent, AnalyticsPlatform } from "@/services/analytics/analyticsService";
|
||||||
|
import { metricService } from "@/services/analytics/metricService";
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
const locale = "en";
|
const locale = "en";
|
||||||
|
|
||||||
interface IUseAuthProps {
|
interface IUseAuthProps {
|
||||||
funnelId: string;
|
funnelId: string;
|
||||||
|
googleAnalyticsId?: string;
|
||||||
/**
|
/**
|
||||||
* Дополнительные данные для регистрации пользователя.
|
* Дополнительные данные для регистрации пользователя.
|
||||||
* Будут объединены с базовым payload при авторизации.
|
* Будут объединены с базовым payload при авторизации.
|
||||||
@ -22,8 +24,8 @@ interface IUseAuthProps {
|
|||||||
registrationData?: Record<string, any>;
|
registrationData?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAuth = ({ funnelId, registrationData }: IUseAuthProps) => {
|
export const useAuth = ({ funnelId, googleAnalyticsId, registrationData }: IUseAuthProps) => {
|
||||||
const { updateSession } = useSession({ funnelId });
|
const { updateSession } = useSession({ funnelId, googleAnalyticsId });
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@ -108,6 +110,19 @@ export const useAuth = ({ funnelId, registrationData }: IUseAuthProps) => {
|
|||||||
source: funnelId,
|
source: funnelId,
|
||||||
UserID: userId,
|
UserID: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ✅ Отправляем UserID и email в userParams (параметры посетителя)
|
||||||
|
metricService.setUserID(userId);
|
||||||
|
metricService.userParams({
|
||||||
|
UserID: userId,
|
||||||
|
email,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ Отправляем email и userId в params (параметры визита)
|
||||||
|
metricService.sendVisitContext({
|
||||||
|
email,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await setAuthTokenToCookie(token);
|
await setAuthTokenToCookie(token);
|
||||||
|
|||||||
@ -10,15 +10,17 @@ import { getClientTimezone } from "@/shared/utils/locales";
|
|||||||
import { parseQueryParams } from "@/shared/utils/url";
|
import { parseQueryParams } from "@/shared/utils/url";
|
||||||
import { useCallback, useMemo, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
import { setSessionIdToCookie } from "@/entities/session/serverActions";
|
import { setSessionIdToCookie } from "@/entities/session/serverActions";
|
||||||
|
import { metricService } from "@/services/analytics/metricService";
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
const locale = "en";
|
const locale = "en";
|
||||||
|
|
||||||
interface IUseSessionProps {
|
interface IUseSessionProps {
|
||||||
funnelId: string;
|
funnelId: string;
|
||||||
|
googleAnalyticsId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSession = ({ funnelId }: IUseSessionProps) => {
|
export const useSession = ({ funnelId, googleAnalyticsId }: IUseSessionProps) => {
|
||||||
const localStorageKey = `${funnelId}_sessionId`;
|
const localStorageKey = `${funnelId}_sessionId`;
|
||||||
const sessionId =
|
const sessionId =
|
||||||
typeof window === "undefined" ? "" : localStorage.getItem(localStorageKey);
|
typeof window === "undefined" ? "" : localStorage.getItem(localStorageKey);
|
||||||
@ -70,6 +72,19 @@ export const useSession = ({ funnelId }: IUseSessionProps) => {
|
|||||||
sessionFromServer?.status === "success"
|
sessionFromServer?.status === "success"
|
||||||
) {
|
) {
|
||||||
await setSessionId(sessionFromServer.sessionId);
|
await setSessionId(sessionFromServer.sessionId);
|
||||||
|
|
||||||
|
// ✅ Отправляем sessionId в userParams (параметры посетителя)
|
||||||
|
metricService.userParams({
|
||||||
|
sessionId: sessionFromServer.sessionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ Отправляем контекст визита в params (параметры визита)
|
||||||
|
metricService.sendVisitContext({
|
||||||
|
sessionId: sessionFromServer.sessionId,
|
||||||
|
funnelId,
|
||||||
|
gaId: googleAnalyticsId,
|
||||||
|
});
|
||||||
|
|
||||||
return sessionFromServer;
|
return sessionFromServer;
|
||||||
}
|
}
|
||||||
console.error(
|
console.error(
|
||||||
@ -89,7 +104,7 @@ export const useSession = ({ funnelId }: IUseSessionProps) => {
|
|||||||
sessionId: "",
|
sessionId: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [sessionId, timezone, setSessionId, funnelId]);
|
}, [sessionId, timezone, setSessionId, funnelId, googleAnalyticsId]);
|
||||||
|
|
||||||
const updateSession = useCallback(
|
const updateSession = useCallback(
|
||||||
async (data: IUpdateSessionRequest["data"]) => {
|
async (data: IUpdateSessionRequest["data"]) => {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { useSession } from "@/hooks/session/useSession";
|
|||||||
interface UnleashSessionProviderProps {
|
interface UnleashSessionProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
funnelId: string;
|
funnelId: string;
|
||||||
|
googleAnalyticsId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,10 +16,11 @@ interface UnleashSessionProviderProps {
|
|||||||
export function UnleashSessionProvider({
|
export function UnleashSessionProvider({
|
||||||
children,
|
children,
|
||||||
funnelId,
|
funnelId,
|
||||||
|
googleAnalyticsId,
|
||||||
}: UnleashSessionProviderProps) {
|
}: UnleashSessionProviderProps) {
|
||||||
const [sessionId, setSessionId] = useState<string | null>(null);
|
const [sessionId, setSessionId] = useState<string | null>(null);
|
||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
const { createSession } = useSession({ funnelId });
|
const { createSession } = useSession({ funnelId, googleAnalyticsId });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initSession = async () => {
|
const initSession = async () => {
|
||||||
|
|||||||
@ -65,7 +65,9 @@ export function useUnleashAnalytics() {
|
|||||||
console.warn("⚠️ [Google Analytics] Not available - gtag function not found");
|
console.warn("⚠️ [Google Analytics] Not available - gtag function not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 2. Отправляем в Яндекс Метрику через params (параметры визита)
|
// ✅ 2. Отправляем в Яндекс Метрику через params (параметры ВИЗИТА)
|
||||||
|
// ВАЖНО: AB тесты - это параметры ВИЗИТА (params), а не посетителя (userParams)
|
||||||
|
// Идентично aura-webapp: используем прямой вызов window.ym()
|
||||||
const ymAvailable = typeof window !== "undefined" && typeof window.ym !== "undefined";
|
const ymAvailable = typeof window !== "undefined" && typeof window.ym !== "undefined";
|
||||||
const counterId = typeof window !== "undefined" ? window.__YM_COUNTER_ID__ : undefined;
|
const counterId = typeof window !== "undefined" ? window.__YM_COUNTER_ID__ : undefined;
|
||||||
|
|
||||||
@ -74,7 +76,6 @@ export function useUnleashAnalytics() {
|
|||||||
// ym() накопит вызов в очереди если скрипт еще не загрузился
|
// ym() накопит вызов в очереди если скрипт еще не загрузился
|
||||||
window.ym(counterId, 'params', {
|
window.ym(counterId, 'params', {
|
||||||
[`ab_test_${impressionEvent.featureName}`]: impressionEvent.variant,
|
[`ab_test_${impressionEvent.featureName}`]: impressionEvent.variant,
|
||||||
ab_test_app: impressionEvent.context.appName || "witlab-funnel",
|
|
||||||
});
|
});
|
||||||
console.log("✅ [Yandex Metrika] AB test params sent:", {
|
console.log("✅ [Yandex Metrika] AB test params sent:", {
|
||||||
counterId,
|
counterId,
|
||||||
|
|||||||
338
src/services/analytics/metricService.ts
Normal file
338
src/services/analytics/metricService.ts
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
/**
|
||||||
|
* 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<IUserParams>): 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<string, unknown>): 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Извлекает данные для метрики из session data
|
||||||
|
* Отправляет только релевантные поля: gender, age, partnerGender, partnerAge
|
||||||
|
*/
|
||||||
|
const sendSessionDataToMetrics = (sessionData: Record<string, unknown>): void => {
|
||||||
|
const metrics: Partial<IUserParams> = {};
|
||||||
|
|
||||||
|
// Извлекаем gender (может быть в profile.gender или просто gender)
|
||||||
|
if (typeof sessionData.gender === 'string') {
|
||||||
|
metrics.gender = sessionData.gender;
|
||||||
|
} else if (sessionData.profile && typeof sessionData.profile === 'object') {
|
||||||
|
const profile = sessionData.profile as Record<string, unknown>;
|
||||||
|
if (typeof profile.gender === 'string') {
|
||||||
|
metrics.gender = profile.gender;
|
||||||
|
}
|
||||||
|
if (typeof profile.partnerGender === 'string') {
|
||||||
|
metrics.partnerGender = profile.partnerGender;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Извлекаем partner gender (может быть в partner.gender или partnerGender)
|
||||||
|
if (typeof sessionData.partnerGender === 'string') {
|
||||||
|
metrics.partnerGender = sessionData.partnerGender;
|
||||||
|
} else if (sessionData.partner && typeof sessionData.partner === 'object') {
|
||||||
|
const partner = sessionData.partner as Record<string, unknown>;
|
||||||
|
if (typeof partner.gender === 'string') {
|
||||||
|
metrics.partnerGender = partner.gender;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вычисляем age из birthdate если есть
|
||||||
|
if (typeof sessionData.birthdate === 'string') {
|
||||||
|
const age = calculateAge(sessionData.birthdate);
|
||||||
|
if (age) metrics.age = age;
|
||||||
|
} else if (sessionData.profile && typeof sessionData.profile === 'object') {
|
||||||
|
const profile = sessionData.profile as Record<string, unknown>;
|
||||||
|
if (typeof profile.birthdate === 'string') {
|
||||||
|
const age = calculateAge(profile.birthdate);
|
||||||
|
if (age) metrics.age = age;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вычисляем partner age если есть
|
||||||
|
if (typeof sessionData.partnerBirthdate === 'string') {
|
||||||
|
const age = calculateAge(sessionData.partnerBirthdate);
|
||||||
|
if (age) metrics.partnerAge = age;
|
||||||
|
} else if (sessionData.partner && typeof sessionData.partner === 'object') {
|
||||||
|
const partner = sessionData.partner as Record<string, unknown>;
|
||||||
|
if (typeof partner.birthdate === 'string') {
|
||||||
|
const age = calculateAge(partner.birthdate);
|
||||||
|
if (age) metrics.partnerAge = age;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отправляем только если есть данные
|
||||||
|
if (Object.keys(metrics).length > 0) {
|
||||||
|
userParams(metrics);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Вычисляет возраст из даты рождения
|
||||||
|
* @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<IVisitParams>): 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,
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user