Skip to content

Commit a1b516c

Browse files
authored
Merge pull request #1973 from SUI-Components/revert-1972-improve-session-id-assignment
Revert "fix(packages/sui-segment-wrapper): improve session id assignment"
2 parents 08c3946 + 6683739 commit a1b516c

3 files changed

Lines changed: 20 additions & 167 deletions

File tree

packages/sui-segment-wrapper/src/index.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ if (isClient && window.analytics) {
5151

5252
if (googleAnalyticsMeasurementId) {
5353
const googleAnalyticsConfig = getConfig('googleAnalyticsConfig')
54-
const cookiePrefix = getConfig('googleAnalyticsCookiePrefix') || 'segment'
5554

5655
window[dataLayerName] = window[dataLayerName] || []
5756
window.gtag =
@@ -63,7 +62,7 @@ if (isClient && window.analytics) {
6362
window.gtag('js', new Date())
6463
if (needsConsentManagement) sendGoogleConsents()
6564
window.gtag('config', googleAnalyticsMeasurementId, {
66-
cookie_prefix: cookiePrefix,
65+
cookie_prefix: 'segment',
6766
send_page_view: false,
6867
...googleAnalyticsConfig,
6968
...getCampaignDetails()
@@ -81,4 +80,3 @@ export default analytics
8180
export {getAdobeVisitorData, getAdobeMCVisitorID} from './repositories/adobeRepository.js'
8281
export {getUniversalId} from './universalId.js'
8382
export {EVENTS} from './events.js'
84-
export {getGA4Data, getGoogleClientId, getGoogleSessionId} from './repositories/googleRepository.js'

packages/sui-segment-wrapper/src/repositories/googleRepository.js

Lines changed: 19 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {dispatchEvent} from '@s-ui/js/lib/events'
33
import {getConfig} from '../config.js'
44
import {EVENTS} from '../events.js'
55
import {utils} from '../middlewares/source/pageReferrer.js'
6-
import {getGA4SessionIdFromCookie} from '../utils/cookies.js'
76

87
const 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

148103
const getGoogleField = async field => {
@@ -229,89 +184,14 @@ function readFromUtm(searchParams) {
229184
}
230185

231186
export 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-
*/
260187
export 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).
317197
export function getGoogleConsentValue(consentType = 'analytics_storage') {

packages/sui-segment-wrapper/src/utils/cookies.js

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,6 @@ export function readCookie(cookieName) {
44
return value !== null ? unescape(value[1]) : null
55
}
66

7-
/**
8-
* Reads the GA4 session ID directly from the cookie to avoid race conditions with gtag API.
9-
* The cookie format is: _ga_<CONTAINER_ID>=GS1.1.<sessionId>.<timestamp>...
10-
*
11-
* @param {string} cookiePrefix - Cookie prefix configured in GA4 (e.g., 'segment')
12-
* @returns {string|null} The session ID or null if not found
13-
*/
14-
export function getGA4SessionIdFromCookie(cookiePrefix = 'segment') {
15-
const cookies = document.cookie.split(';')
16-
const sessionRegex = /\.s(\d+)/
17-
const searchStr = cookiePrefix ? `${cookiePrefix}_ga_` : '_ga_'
18-
19-
for (let i = 0; i < cookies.length; i++) {
20-
const cookie = cookies[i].trim()
21-
if (cookie.indexOf(searchStr) === 0) {
22-
const match = cookie.match(sessionRegex)
23-
if (match && match[1]) {
24-
return match[1]
25-
}
26-
}
27-
}
28-
29-
return null
30-
}
31-
327
const ONE_YEAR = 31_536_000
338
const DEFAULT_PATH = '/'
349
const DEFAULT_SAME_SITE = 'Lax'

0 commit comments

Comments
 (0)