/** * External dependencies */ import type { ConversionEvent, Maybe, TrackEvent } from '@nab/types'; /** * Internal dependencies */ import { getSettings } from '../utils/helpers/internal/get-settings'; import type { ExperimentSummary, Session, Settings } from '../types'; function sendToGA4( eventData: TrackEvent ) { const settings = getSettings(); const goal = eventData.kind === 'conversion' ? getGoal( settings, eventData ) : undefined; if ( false === goal ) { return; } const eventName = // eslint-disable-next-line no-nested-ternary eventData.kind === 'visit' ? 'experiment_impression' : goal ? goal.eventName?.trim() || 'conversion' : 'conversion'; const eventDetails = { experiment_id: eventData.experiment, experiment_name: getExperimentName( settings, eventData ), variant_id: eventData.alternative, variant_name: getAlternativeName( eventData ), ...( goal && eventData.kind === 'conversion' && { goal_id: eventData.goal, ...( goal.goalName && { goal_name: goal.goalName } ), } ), }; if ( ( ! settings?.ga4TrackingMethod || settings.ga4TrackingMethod === 'gtm' ) && hasDataLayer( window ) ) { window.dataLayer.push( { event: eventName, ...eventDetails } ); } else if ( ( ! settings?.ga4TrackingMethod || settings.ga4TrackingMethod === 'gtag' ) && hasGTag( window ) ) { window.gtag( 'event', eventName, eventDetails ); } } const hasGTag = ( win: unknown ): win is { gtag: ( command: string, eventName: string, params?: Record< string, unknown > ) => void; } => !! win && 'object' === typeof win && 'gtag' in win; const hasDataLayer = ( win: unknown ): win is { dataLayer: Array< Record< string, unknown > >; } => !! win && 'object' === typeof win && 'dataLayer' in win; export function initGA4Tracking( session: Session ): void { if ( ! session.isGA4Integrated ) { return; } window.addEventListener( 'nelio-ab-testing/track', ( e: CustomEventInit< { event: TrackEvent } > ) => { if ( e.detail && e.detail.event && [ 'visit', 'conversion' ].includes( e.detail.event.kind ) ) { sendToGA4( e.detail.event ); } } ); } export function getGA4ClientId(): string | undefined { const match = document.cookie.match( /_ga=GA\d\.\d\.(\d+\.\d+)/ ); return match ? match[ 1 ] : undefined; } function getExperimentName( settings: Maybe< Settings >, eventData: TrackEvent ): Maybe< string > { if ( ! settings ) { return undefined; } return settings.experiments.find( ( e ) => e.id === eventData.experiment ) ?.extensions?.ga4?.experimentName; } function getAlternativeName( eventData: TrackEvent ): Maybe< string > { return eventData.alternative === 0 ? 'Control' : `Variant ${ getLetter( eventData.alternative ) }`; } type GA4Goal = Required< Required< ExperimentSummary >[ 'extensions' ] >[ 'ga4' ][ 'goals' ][ number ]; function getGoal( settings: Maybe< Required< Settings > >, eventData: ConversionEvent ): Exclude< GA4Goal | false, undefined > { if ( ! settings ) { return false; } const exp = settings.experiments.find( ( e ) => e.id === eventData.experiment ); if ( ! exp ) { return false; } const goals = exp.extensions?.ga4?.goals ?? []; if ( ! goals.length ) { return false; } return goals[ eventData.goal ] || false; } function getLetter( index: number ): string { const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; if ( index >= letters.length ) { return `${ index }`; } return letters.charAt( index ); }