diff --git a/packages/sui-segment-wrapper/src/index.js b/packages/sui-segment-wrapper/src/index.js index 83411be8e..75d89163c 100644 --- a/packages/sui-segment-wrapper/src/index.js +++ b/packages/sui-segment-wrapper/src/index.js @@ -51,7 +51,6 @@ if (isClient && window.analytics) { if (googleAnalyticsMeasurementId) { const googleAnalyticsConfig = getConfig('googleAnalyticsConfig') - const cookiePrefix = getConfig('googleAnalyticsCookiePrefix') || 'segment' window[dataLayerName] = window[dataLayerName] || [] window.gtag = @@ -63,7 +62,7 @@ if (isClient && window.analytics) { window.gtag('js', new Date()) if (needsConsentManagement) sendGoogleConsents() window.gtag('config', googleAnalyticsMeasurementId, { - cookie_prefix: cookiePrefix, + cookie_prefix: 'segment', send_page_view: false, ...googleAnalyticsConfig, ...getCampaignDetails() @@ -81,4 +80,3 @@ export default analytics export {getAdobeVisitorData, getAdobeMCVisitorID} from './repositories/adobeRepository.js' export {getUniversalId} from './universalId.js' export {EVENTS} from './events.js' -export {getGA4Data, getGoogleClientId, getGoogleSessionId} from './repositories/googleRepository.js' diff --git a/packages/sui-segment-wrapper/src/repositories/googleRepository.js b/packages/sui-segment-wrapper/src/repositories/googleRepository.js index ab5c9097e..343fb2650 100644 --- a/packages/sui-segment-wrapper/src/repositories/googleRepository.js +++ b/packages/sui-segment-wrapper/src/repositories/googleRepository.js @@ -3,7 +3,6 @@ import {dispatchEvent} from '@s-ui/js/lib/events' import {getConfig} from '../config.js' import {EVENTS} from '../events.js' import {utils} from '../middlewares/source/pageReferrer.js' -import {getGA4SessionIdFromCookie} from '../utils/cookies.js' const FIELDS = { clientId: 'client_id', @@ -72,77 +71,33 @@ export const loadGoogleAnalytics = async () => { return loadScript(gtagScript) } -/** - * Checks if a session is new by comparing with localStorage. - * This function is idempotent and safe to call multiple times with the same sessionId. - * - * @param {string} sessionId - Current session ID - * @returns {{isNewSession: boolean, cachedSessionId: string|null}} - */ -const checkNewSession = sessionId => { - const storageKey = 'ga_session_id' - let cachedSessionId = null - - try { - cachedSessionId = window.localStorage.getItem(storageKey) - } catch (e) { - // localStorage might not be available - return {isNewSession: false, cachedSessionId: null} - } - - const isNewSession = String(cachedSessionId) !== String(sessionId) - - // Only update localStorage if it's actually a new session - if (isNewSession && sessionId) { - try { - window.localStorage.setItem(storageKey, sessionId) - } catch (e) { - // localStorage might not be available - } - } - - return {isNewSession, cachedSessionId} -} - -/** - * Trigger GA init event just once per session. - * Uses checkNewSession to detect if it's a new session before sending the event. - * - * @param {string} sessionId - Current session ID - * @param {boolean} isNewSession - Whether this is a new session (from checkNewSession) - */ -const triggerGoogleAnalyticsInitEvent = (sessionId, isNewSession) => { +// Trigger GA init event just once per session. +const triggerGoogleAnalyticsInitEvent = sessionId => { const eventName = getConfig('googleAnalyticsInitEvent') ?? DEFAULT_GA_INIT_EVENT const eventPrefix = `ga_event_${eventName}_` const eventKey = `${eventPrefix}${sessionId}` if (typeof window.gtag === 'undefined') return - // Only send event if it's a new session and we haven't sent it yet - try { - const alreadySent = localStorage.getItem(eventKey) + // Check if the event has already been sent in this session. + if (!localStorage.getItem(eventKey)) { + // If not, send it. + window.gtag('event', eventName) - if (isNewSession && !alreadySent && sessionId) { - // Send the event - window.gtag('event', eventName) - - // eslint-disable-next-line no-console - console.log(`Sending GA4 event "${eventName}" for the session "${sessionId}"`) + // eslint-disable-next-line no-console + console.log(`Sending GA4 event "${eventName}" for the session "${sessionId}"`) - // Mark as sent - localStorage.setItem(eventKey, 'true') - dispatchEvent({eventName: EVENTS.GA4_INIT_EVENT_SENT, detail: {eventName, sessionId}}) + // And then save a new GA session hit in local storage. + localStorage.setItem(eventKey, 'true') + dispatchEvent({eventName: EVENTS.GA4_INIT_EVENT_SENT, detail: {eventName, sessionId}}) + } - // Clean old GA sessions hits from the storage - Object.keys(localStorage).forEach(key => { - if (key.startsWith(eventPrefix) && key !== eventKey) { - localStorage.removeItem(key) - } - }) + // Clean old GA sessions hits from the storage. + Object.keys(localStorage).forEach(key => { + if (key.startsWith(eventPrefix) && key !== eventKey) { + localStorage.removeItem(key) } - } catch (e) { - // localStorage might not be available - } + }) } const getGoogleField = async field => { @@ -229,89 +184,14 @@ function readFromUtm(searchParams) { } export const getGoogleClientId = async () => getGoogleField(FIELDS.clientId) - -/** - * Exposes GA4 data to window for debugging and compatibility. - * Also resolves a global promise if available (window.resolveGAData). - * - * @param {object} gaData - GA4 data object - */ -const exposeGA4Data = gaData => { - window.__GA4_DATA = gaData - - if (typeof window.resolveGAData === 'function') { - window.resolveGAData(gaData) - } -} - -// Cache to track if we've already logged the new session -let hasLoggedNewSession = false - -/** - * Gets the Google Analytics session ID, prioritizing the cookie value over the API. - * This avoids race conditions where gtag.get('session_id') returns an incorrect value - * in the first hits before the cookie is fully written. - * - * Also detects and stores new sessions in localStorage for tracking purposes. - * Safe to call multiple times - will only log once per session. - * - * @returns {Promise} The session ID - */ export const getGoogleSessionId = async () => { - const cookiePrefix = getConfig('googleAnalyticsCookiePrefix') || 'segment' - - // First, get the session ID from gtag API (may be incorrect in first hits) - const apiSessionId = await getGoogleField(FIELDS.sessionId) - - // Try to read the session ID directly from the cookie (more reliable) - const cookieSessionId = getGA4SessionIdFromCookie(cookiePrefix) - - // Prioritize cookie value if available, fallback to API - const sessionId = cookieSessionId || apiSessionId - - // Check if this is a new session and store it - const {isNewSession} = checkNewSession(sessionId) + const sessionId = await getGoogleField(FIELDS.sessionId) - // Only log once per session to avoid spam in console - if (isNewSession && sessionId && !hasLoggedNewSession) { - hasLoggedNewSession = true - // eslint-disable-next-line no-console - console.log(`New GA4 session started: ${sessionId} (Source: ${cookieSessionId ? 'Cookie' : 'API'})`) - } else if (!isNewSession) { - // Reset flag if we're back to the same session - hasLoggedNewSession = false - } - - // Trigger GA4 init event if it's a new session - triggerGoogleAnalyticsInitEvent(sessionId, isNewSession) + triggerGoogleAnalyticsInitEvent(sessionId) return sessionId } -/** - * Gets both client ID and session ID from GA4 and exposes them globally. - * This is useful for debugging and ensures data consistency. - * - * @returns {Promise<{clientId: string, sessionId: string, cachedSessionId: string, isNewSession: boolean}>} - */ -export const getGA4Data = async () => { - const [clientId, sessionId] = await Promise.all([getGoogleClientId(), getGoogleSessionId()]) - - // Reuse the session check logic - const {isNewSession, cachedSessionId} = checkNewSession(sessionId) - - const gaData = { - clientId, - sessionId, - cachedSessionId, - isNewSession - } - - exposeGA4Data(gaData) - - return gaData -} - // Unified consent state getter. // Returns GRANTED, DENIED or undefined (default / unknown / unavailable). export function getGoogleConsentValue(consentType = 'analytics_storage') { diff --git a/packages/sui-segment-wrapper/src/utils/cookies.js b/packages/sui-segment-wrapper/src/utils/cookies.js index f9a6380bc..0f731ad0b 100644 --- a/packages/sui-segment-wrapper/src/utils/cookies.js +++ b/packages/sui-segment-wrapper/src/utils/cookies.js @@ -4,31 +4,6 @@ export function readCookie(cookieName) { return value !== null ? unescape(value[1]) : null } -/** - * Reads the GA4 session ID directly from the cookie to avoid race conditions with gtag API. - * The cookie format is: _ga_=GS1.1..... - * - * @param {string} cookiePrefix - Cookie prefix configured in GA4 (e.g., 'segment') - * @returns {string|null} The session ID or null if not found - */ -export function getGA4SessionIdFromCookie(cookiePrefix = 'segment') { - const cookies = document.cookie.split(';') - const sessionRegex = /\.s(\d+)/ - const searchStr = cookiePrefix ? `${cookiePrefix}_ga_` : '_ga_' - - for (let i = 0; i < cookies.length; i++) { - const cookie = cookies[i].trim() - if (cookie.indexOf(searchStr) === 0) { - const match = cookie.match(sessionRegex) - if (match && match[1]) { - return match[1] - } - } - } - - return null -} - const ONE_YEAR = 31_536_000 const DEFAULT_PATH = '/' const DEFAULT_SAME_SITE = 'Lax'