@@ -3,7 +3,6 @@ import {dispatchEvent} from '@s-ui/js/lib/events'
33import { getConfig } from '../config.js'
44import { EVENTS } from '../events.js'
55import { utils } from '../middlewares/source/pageReferrer.js'
6- import { getGA4SessionIdFromCookie } from '../utils/cookies.js'
76
87const FIELDS = {
98 clientId : 'client_id' ,
@@ -72,77 +71,33 @@ export const loadGoogleAnalytics = async () => {
7271 return loadScript ( gtagScript )
7372}
7473
75- /**
76- * Checks if a session is new by comparing with localStorage.
77- * This function is idempotent and safe to call multiple times with the same sessionId.
78- *
79- * @param {string } sessionId - Current session ID
80- * @returns {{isNewSession: boolean, cachedSessionId: string|null} }
81- */
82- const checkNewSession = sessionId => {
83- const storageKey = 'ga_session_id'
84- let cachedSessionId = null
85-
86- try {
87- cachedSessionId = window . localStorage . getItem ( storageKey )
88- } catch ( e ) {
89- // localStorage might not be available
90- return { isNewSession : false , cachedSessionId : null }
91- }
92-
93- const isNewSession = String ( cachedSessionId ) !== String ( sessionId )
94-
95- // Only update localStorage if it's actually a new session
96- if ( isNewSession && sessionId ) {
97- try {
98- window . localStorage . setItem ( storageKey , sessionId )
99- } catch ( e ) {
100- // localStorage might not be available
101- }
102- }
103-
104- return { isNewSession, cachedSessionId}
105- }
106-
107- /**
108- * Trigger GA init event just once per session.
109- * Uses checkNewSession to detect if it's a new session before sending the event.
110- *
111- * @param {string } sessionId - Current session ID
112- * @param {boolean } isNewSession - Whether this is a new session (from checkNewSession)
113- */
114- const triggerGoogleAnalyticsInitEvent = ( sessionId , isNewSession ) => {
74+ // Trigger GA init event just once per session.
75+ const triggerGoogleAnalyticsInitEvent = sessionId => {
11576 const eventName = getConfig ( 'googleAnalyticsInitEvent' ) ?? DEFAULT_GA_INIT_EVENT
11677 const eventPrefix = `ga_event_${ eventName } _`
11778 const eventKey = `${ eventPrefix } ${ sessionId } `
11879
11980 if ( typeof window . gtag === 'undefined' ) return
12081
121- // Only send event if it's a new session and we haven't sent it yet
122- try {
123- const alreadySent = localStorage . getItem ( eventKey )
82+ // Check if the event has already been sent in this session.
83+ if ( ! localStorage . getItem ( eventKey ) ) {
84+ // If not, send it.
85+ window . gtag ( 'event' , eventName )
12486
125- if ( isNewSession && ! alreadySent && sessionId ) {
126- // Send the event
127- window . gtag ( 'event' , eventName )
128-
129- // eslint-disable-next-line no-console
130- console . log ( `Sending GA4 event "${ eventName } " for the session "${ sessionId } "` )
87+ // eslint-disable-next-line no-console
88+ console . log ( `Sending GA4 event "${ eventName } " for the session "${ sessionId } "` )
13189
132- // Mark as sent
133- localStorage . setItem ( eventKey , 'true' )
134- dispatchEvent ( { eventName : EVENTS . GA4_INIT_EVENT_SENT , detail : { eventName, sessionId} } )
90+ // And then save a new GA session hit in local storage.
91+ localStorage . setItem ( eventKey , 'true' )
92+ dispatchEvent ( { eventName : EVENTS . GA4_INIT_EVENT_SENT , detail : { eventName, sessionId} } )
93+ }
13594
136- // Clean old GA sessions hits from the storage
137- Object . keys ( localStorage ) . forEach ( key => {
138- if ( key . startsWith ( eventPrefix ) && key !== eventKey ) {
139- localStorage . removeItem ( key )
140- }
141- } )
95+ // Clean old GA sessions hits from the storage.
96+ Object . keys ( localStorage ) . forEach ( key => {
97+ if ( key . startsWith ( eventPrefix ) && key !== eventKey ) {
98+ localStorage . removeItem ( key )
14299 }
143- } catch ( e ) {
144- // localStorage might not be available
145- }
100+ } )
146101}
147102
148103const getGoogleField = async field => {
@@ -229,89 +184,14 @@ function readFromUtm(searchParams) {
229184}
230185
231186export const getGoogleClientId = async ( ) => getGoogleField ( FIELDS . clientId )
232-
233- /**
234- * Exposes GA4 data to window for debugging and compatibility.
235- * Also resolves a global promise if available (window.resolveGAData).
236- *
237- * @param {object } gaData - GA4 data object
238- */
239- const exposeGA4Data = gaData => {
240- window . __GA4_DATA = gaData
241-
242- if ( typeof window . resolveGAData === 'function' ) {
243- window . resolveGAData ( gaData )
244- }
245- }
246-
247- // Cache to track if we've already logged the new session
248- let hasLoggedNewSession = false
249-
250- /**
251- * Gets the Google Analytics session ID, prioritizing the cookie value over the API.
252- * This avoids race conditions where gtag.get('session_id') returns an incorrect value
253- * in the first hits before the cookie is fully written.
254- *
255- * Also detects and stores new sessions in localStorage for tracking purposes.
256- * Safe to call multiple times - will only log once per session.
257- *
258- * @returns {Promise<string> } The session ID
259- */
260187export const getGoogleSessionId = async ( ) => {
261- const cookiePrefix = getConfig ( 'googleAnalyticsCookiePrefix' ) || 'segment'
262-
263- // First, get the session ID from gtag API (may be incorrect in first hits)
264- const apiSessionId = await getGoogleField ( FIELDS . sessionId )
265-
266- // Try to read the session ID directly from the cookie (more reliable)
267- const cookieSessionId = getGA4SessionIdFromCookie ( cookiePrefix )
268-
269- // Prioritize cookie value if available, fallback to API
270- const sessionId = cookieSessionId || apiSessionId
271-
272- // Check if this is a new session and store it
273- const { isNewSession} = checkNewSession ( sessionId )
188+ const sessionId = await getGoogleField ( FIELDS . sessionId )
274189
275- // Only log once per session to avoid spam in console
276- if ( isNewSession && sessionId && ! hasLoggedNewSession ) {
277- hasLoggedNewSession = true
278- // eslint-disable-next-line no-console
279- console . log ( `New GA4 session started: ${ sessionId } (Source: ${ cookieSessionId ? 'Cookie' : 'API' } )` )
280- } else if ( ! isNewSession ) {
281- // Reset flag if we're back to the same session
282- hasLoggedNewSession = false
283- }
284-
285- // Trigger GA4 init event if it's a new session
286- triggerGoogleAnalyticsInitEvent ( sessionId , isNewSession )
190+ triggerGoogleAnalyticsInitEvent ( sessionId )
287191
288192 return sessionId
289193}
290194
291- /**
292- * Gets both client ID and session ID from GA4 and exposes them globally.
293- * This is useful for debugging and ensures data consistency.
294- *
295- * @returns {Promise<{clientId: string, sessionId: string, cachedSessionId: string, isNewSession: boolean}> }
296- */
297- export const getGA4Data = async ( ) => {
298- const [ clientId , sessionId ] = await Promise . all ( [ getGoogleClientId ( ) , getGoogleSessionId ( ) ] )
299-
300- // Reuse the session check logic
301- const { isNewSession, cachedSessionId} = checkNewSession ( sessionId )
302-
303- const gaData = {
304- clientId,
305- sessionId,
306- cachedSessionId,
307- isNewSession
308- }
309-
310- exposeGA4Data ( gaData )
311-
312- return gaData
313- }
314-
315195// Unified consent state getter.
316196// Returns GRANTED, DENIED or undefined (default / unknown / unavailable).
317197export function getGoogleConsentValue ( consentType = 'analytics_storage' ) {
0 commit comments