@ohos-ports/s-ui-segment-wrapper 0.0.0-stage → 5.4.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +269 -2
- package/lib/config.js +20 -0
- package/lib/events.js +3 -0
- package/lib/index.js +82 -0
- package/lib/middlewares/destination/optimizelySiteAttribute.js +17 -0
- package/lib/middlewares/source/campaignContext.js +10 -0
- package/lib/middlewares/source/defaultContextProperties.js +11 -0
- package/lib/middlewares/source/optimizelyUserId.js +15 -0
- package/lib/middlewares/source/pageData.js +42 -0
- package/lib/middlewares/source/pageReferrer.js +102 -0
- package/lib/middlewares/source/userScreenInfo.js +12 -0
- package/lib/middlewares/source/userTraits.js +48 -0
- package/lib/repositories/googleRepository.js +272 -0
- package/lib/repositories/xandrRepository.js +42 -0
- package/lib/segmentWrapper.js +364 -0
- package/lib/tcf.js +230 -0
- package/lib/universalId.js +48 -0
- package/lib/utils/checkAnonymousId.js +9 -0
- package/lib/utils/cookies.js +50 -0
- package/lib/utils/ga4Detection.js +18 -0
- package/lib/utils/hashEmail.js +24 -0
- package/lib/utils/patchAnalytics.js +36 -0
- package/lib/utils/storage.js +17 -0
- package/package.json +39 -4
- package/src/config.js +25 -0
- package/src/events.js +3 -0
- package/src/index.js +96 -0
- package/src/middlewares/destination/optimizelySiteAttribute.js +22 -0
- package/src/middlewares/source/campaignContext.js +12 -0
- package/src/middlewares/source/defaultContextProperties.js +15 -0
- package/src/middlewares/source/optimizelyUserId.js +20 -0
- package/src/middlewares/source/pageData.js +45 -0
- package/src/middlewares/source/pageReferrer.js +125 -0
- package/src/middlewares/source/userScreenInfo.js +11 -0
- package/src/middlewares/source/userTraits.js +41 -0
- package/src/repositories/googleRepository.js +251 -0
- package/src/repositories/xandrRepository.js +48 -0
- package/src/segmentWrapper.js +338 -0
- package/src/tcf.js +263 -0
- package/src/universalId.js +51 -0
- package/src/utils/checkAnonymousId.js +10 -0
- package/src/utils/cookies.js +56 -0
- package/src/utils/ga4Detection.js +22 -0
- package/src/utils/hashEmail.js +33 -0
- package/src/utils/patchAnalytics.js +46 -0
- package/src/utils/storage.js +18 -0
- package/src-umd/index.js +8 -0
- package/umd/index.js +1 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const optimizelyUserId = ({payload, next}) => {
|
|
2
|
+
const {
|
|
3
|
+
obj: {integrations}
|
|
4
|
+
} = payload
|
|
5
|
+
|
|
6
|
+
// when integrations.All equals false, destinations are deactivated
|
|
7
|
+
if (integrations.All === false) {
|
|
8
|
+
return next(payload)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
payload.obj.integrations = {
|
|
12
|
+
...integrations,
|
|
13
|
+
Optimizely: {
|
|
14
|
+
userId: window.analytics.user().anonymousId(),
|
|
15
|
+
...integrations.Optimizely
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
next(payload)
|
|
20
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const createPageDataMiddleware = () => {
|
|
2
|
+
let last = null
|
|
3
|
+
|
|
4
|
+
return ({payload, next}) => {
|
|
5
|
+
try {
|
|
6
|
+
const {obj} = payload
|
|
7
|
+
const {event, type} = obj
|
|
8
|
+
|
|
9
|
+
if (type !== 'track') {
|
|
10
|
+
next(payload)
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (typeof event === 'string' && event.endsWith('Viewed')) {
|
|
15
|
+
const {page_name: name, page_type: type} = obj.properties || {}
|
|
16
|
+
last = {name, type}
|
|
17
|
+
|
|
18
|
+
next(payload)
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!last) {
|
|
23
|
+
next(payload)
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
next({
|
|
28
|
+
...payload,
|
|
29
|
+
obj: {
|
|
30
|
+
...obj,
|
|
31
|
+
properties: {
|
|
32
|
+
...obj.properties,
|
|
33
|
+
page_name_origin: last.name,
|
|
34
|
+
page_type: obj.properties?.page_type || last.type
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error(error) // eslint-disable-line
|
|
40
|
+
next(payload)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const pageData = createPageDataMiddleware()
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {isGA4DestinationEnabled} from '../../utils/ga4Detection.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Capture document.referrer at module load time, before Safari ITP can clear it.
|
|
5
|
+
* This ensures the first page event has the real referrer from the external source.
|
|
6
|
+
*/
|
|
7
|
+
export const INITIAL_DOCUMENT_REFERRER = typeof document !== 'undefined' ? document.referrer : ''
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Capture window.location.search at module load time, before Safari ITP can
|
|
11
|
+
* strip tracking parameters via redirects.
|
|
12
|
+
*/
|
|
13
|
+
export const INITIAL_SEARCH_STRING = typeof window !== 'undefined' ? window.location.search : ''
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Capture initial URL at module load time
|
|
17
|
+
*/
|
|
18
|
+
const INITIAL_URL = typeof window !== 'undefined' ? window.location.href : ''
|
|
19
|
+
|
|
20
|
+
export const referrerState = {
|
|
21
|
+
spaReferrer: '',
|
|
22
|
+
referrer: INITIAL_DOCUMENT_REFERRER,
|
|
23
|
+
isFirstPageViewSent: false
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Useful wrapper around document and window objects
|
|
28
|
+
*/
|
|
29
|
+
export const utils = {
|
|
30
|
+
/**
|
|
31
|
+
* @returns {string} The referrer of the document
|
|
32
|
+
*/
|
|
33
|
+
getDocumentReferrer: () => document.referrer,
|
|
34
|
+
/**
|
|
35
|
+
* @returns {string} The actual location with protocol, domain and pathname
|
|
36
|
+
*/
|
|
37
|
+
getActualLocation: () => {
|
|
38
|
+
const {origin, pathname} = window.location
|
|
39
|
+
return `${origin}${pathname}`
|
|
40
|
+
},
|
|
41
|
+
/**
|
|
42
|
+
* @returns {string} The actual query string captured at module load (protected from Safari ITP)
|
|
43
|
+
*/
|
|
44
|
+
getActualQueryString: () => {
|
|
45
|
+
// In tests using window.history.pushState, return current search if different from initial
|
|
46
|
+
if (typeof window !== 'undefined' && window.location.search && window.location.search !== INITIAL_SEARCH_STRING) {
|
|
47
|
+
return window.location.search
|
|
48
|
+
}
|
|
49
|
+
return INITIAL_SEARCH_STRING
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get the correct page referrer for SPA navigations
|
|
55
|
+
* @returns {string} referrer
|
|
56
|
+
*/
|
|
57
|
+
export const getPageReferrer = ({isPageTrack = false} = {}) => {
|
|
58
|
+
const {referrer, spaReferrer} = referrerState
|
|
59
|
+
|
|
60
|
+
if (isPageTrack) {
|
|
61
|
+
// For page events, use referrer (initially from INITIAL_DOCUMENT_REFERRER, then from previous page location)
|
|
62
|
+
return referrer
|
|
63
|
+
} else {
|
|
64
|
+
// For track events, use spaReferrer if available, otherwise fall back to referrer
|
|
65
|
+
// This handles the case where a track happens before the first page event
|
|
66
|
+
return spaReferrer || referrer
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Update page referrer for SPA navigations
|
|
72
|
+
*/
|
|
73
|
+
export const updatePageReferrer = () => {
|
|
74
|
+
referrerState.spaReferrer = getPageReferrer({isPageTrack: true})
|
|
75
|
+
// mutate actualReferrer with what will be the new referrer
|
|
76
|
+
referrerState.referrer = utils.getActualLocation()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {object} params
|
|
81
|
+
* @param {{obj: {context: object }}} params.payload
|
|
82
|
+
* @param {function} params.next
|
|
83
|
+
* @returns {void}
|
|
84
|
+
*/
|
|
85
|
+
export const pageReferrer = ({payload, next}) => {
|
|
86
|
+
const {
|
|
87
|
+
obj: {context}
|
|
88
|
+
} = payload
|
|
89
|
+
|
|
90
|
+
const {isPageTrack} = context
|
|
91
|
+
|
|
92
|
+
const referrer = getPageReferrer({isPageTrack})
|
|
93
|
+
|
|
94
|
+
let props = {}
|
|
95
|
+
|
|
96
|
+
if (isPageTrack && !referrerState.isFirstPageViewSent) {
|
|
97
|
+
referrerState.isFirstPageViewSent = true
|
|
98
|
+
props = {
|
|
99
|
+
url: INITIAL_URL,
|
|
100
|
+
search: INITIAL_SEARCH_STRING
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
payload.obj.context = {
|
|
105
|
+
...context,
|
|
106
|
+
page: {
|
|
107
|
+
...context.page,
|
|
108
|
+
...props,
|
|
109
|
+
referrer
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// In NEW mode (client-side destination), also add referrer to properties
|
|
114
|
+
// GA4 Web destination uses properties.page_referrer for attribution
|
|
115
|
+
if (referrer && isGA4DestinationEnabled()) {
|
|
116
|
+
payload.obj.properties = {
|
|
117
|
+
...payload.obj.properties,
|
|
118
|
+
page_referrer: referrer
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// update page referrer only if it's a page event
|
|
123
|
+
isPageTrack && updatePageReferrer()
|
|
124
|
+
next(payload)
|
|
125
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {getConfig} from '../../config.js'
|
|
2
|
+
import {checkAnalyticsGdprIsAccepted, getGdprPrivacyValue} from '../../tcf.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Get user traits from global analytics object and put in the object
|
|
6
|
+
* @param {string} gdprPrivacyValue Determine if we have user consents
|
|
7
|
+
* @returns {object} User traits with to add
|
|
8
|
+
*/
|
|
9
|
+
const getUserTraits = gdprPrivacyValue => {
|
|
10
|
+
const isUserTraitsEnabled = getConfig('isUserTraitsEnabled')
|
|
11
|
+
const user = window.analytics.user()
|
|
12
|
+
const isGdprAccepted = checkAnalyticsGdprIsAccepted(gdprPrivacyValue)
|
|
13
|
+
const userId = user.id()
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
anonymousId: user.anonymousId(),
|
|
17
|
+
...(userId && {userId}),
|
|
18
|
+
...(isGdprAccepted && isUserTraitsEnabled && user.traits())
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const userTraits = async ({payload, next}) => {
|
|
23
|
+
const gdprPrivacyValue = await getGdprPrivacyValue()
|
|
24
|
+
|
|
25
|
+
let userTraits
|
|
26
|
+
try {
|
|
27
|
+
userTraits = getUserTraits(gdprPrivacyValue)
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(error) // eslint-disable-line
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
payload.obj.context = {
|
|
33
|
+
...payload.obj.context,
|
|
34
|
+
traits: {
|
|
35
|
+
...payload.obj.context.traits,
|
|
36
|
+
...userTraits
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
next(payload)
|
|
41
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import {dispatchEvent} from '@s-ui/js/lib/events'
|
|
2
|
+
|
|
3
|
+
import {getConfig} from '../config.js'
|
|
4
|
+
import {EVENTS} from '../events.js'
|
|
5
|
+
import {utils} from '../middlewares/source/pageReferrer.js'
|
|
6
|
+
import {isGA4DestinationEnabled} from '../utils/ga4Detection.js'
|
|
7
|
+
|
|
8
|
+
const FIELDS = {
|
|
9
|
+
clientId: 'client_id',
|
|
10
|
+
sessionId: 'session_id'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_DATA_LAYER_NAME = 'dataLayer'
|
|
14
|
+
|
|
15
|
+
export const CONSENT_STATES = {
|
|
16
|
+
granted: 'granted',
|
|
17
|
+
denied: 'denied'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const CONSENT_STATE_GRANTED_VALUE = 1
|
|
21
|
+
const CONSENT_STATE_DENIED_VALUE = 2
|
|
22
|
+
|
|
23
|
+
const STC = {
|
|
24
|
+
QUERY: 'stc',
|
|
25
|
+
SPLIT_SYMBOL: '-',
|
|
26
|
+
CAMPAIGN_SPLIT_SYMBOL: ':'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const UTM_CAMPAIGN = 'utm_campaign'
|
|
30
|
+
const UTM_MEDIUM = 'utm_medium'
|
|
31
|
+
const UTM_SOURCE = 'utm_source'
|
|
32
|
+
const UTM_CONTENT = 'utm_content'
|
|
33
|
+
const UTM_CAMPAIGN_ID = 'utm_id'
|
|
34
|
+
const UTM_TERM = 'utm_term'
|
|
35
|
+
|
|
36
|
+
const STC_MEDIUM_TRANSFORMATIONS = {
|
|
37
|
+
aff: 'affiliate',
|
|
38
|
+
dis: 'display',
|
|
39
|
+
em: 'email',
|
|
40
|
+
met: 'paid-metasearch',
|
|
41
|
+
sem: 'paid-search',
|
|
42
|
+
rt: 'retargeting',
|
|
43
|
+
sm: 'social-media',
|
|
44
|
+
sp: 'paid-social',
|
|
45
|
+
pn: 'push-notification',
|
|
46
|
+
cs: 'cross-sites'
|
|
47
|
+
}
|
|
48
|
+
const STC_INVALID_CONTENT = 'na'
|
|
49
|
+
const DEFAULT_GA_INIT_EVENT = 'sui'
|
|
50
|
+
|
|
51
|
+
const EMPTY_STC = {medium: null, source: null, campaign: null}
|
|
52
|
+
|
|
53
|
+
const loadScript = async src =>
|
|
54
|
+
new Promise(function (resolve, reject) {
|
|
55
|
+
const script = document.createElement('script')
|
|
56
|
+
|
|
57
|
+
script.src = src
|
|
58
|
+
script.onload = resolve
|
|
59
|
+
script.onerror = reject
|
|
60
|
+
document.head.appendChild(script)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
export const loadGoogleAnalytics = async () => {
|
|
64
|
+
const googleAnalyticsMeasurementId = getConfig('googleAnalyticsMeasurementId')
|
|
65
|
+
const dataLayerName = getConfig('googleAnalyticsDataLayer') || DEFAULT_DATA_LAYER_NAME
|
|
66
|
+
|
|
67
|
+
// Check we have the needed config to load the script
|
|
68
|
+
if (!googleAnalyticsMeasurementId) return Promise.resolve(false)
|
|
69
|
+
// Create the `gtag` script
|
|
70
|
+
const gtagScript = `https://www.googletagmanager.com/gtag/js?id=${googleAnalyticsMeasurementId}&l=${dataLayerName}`
|
|
71
|
+
// Load it and retrieve the `clientId` from Google
|
|
72
|
+
return loadScript(gtagScript)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Trigger GA init event just once per session.
|
|
76
|
+
// Only in LEGACY mode (manual GA4 initialization)
|
|
77
|
+
const triggerGoogleAnalyticsInitEvent = sessionId => {
|
|
78
|
+
// Don't trigger if using Segment destination (new mode)
|
|
79
|
+
if (isGA4DestinationEnabled()) return
|
|
80
|
+
|
|
81
|
+
const eventName = getConfig('googleAnalyticsInitEvent') ?? DEFAULT_GA_INIT_EVENT
|
|
82
|
+
const eventPrefix = `ga_event_${eventName}_`
|
|
83
|
+
const eventKey = `${eventPrefix}${sessionId}`
|
|
84
|
+
|
|
85
|
+
if (typeof window.gtag === 'undefined') return
|
|
86
|
+
|
|
87
|
+
// Check if the event has already been sent in this session.
|
|
88
|
+
if (!localStorage.getItem(eventKey)) {
|
|
89
|
+
// If not, send it.
|
|
90
|
+
window.gtag('event', eventName)
|
|
91
|
+
|
|
92
|
+
// eslint-disable-next-line no-console
|
|
93
|
+
console.log(`Sending GA4 event "${eventName}" for the session "${sessionId}"`)
|
|
94
|
+
|
|
95
|
+
// And then save a new GA session hit in local storage.
|
|
96
|
+
localStorage.setItem(eventKey, 'true')
|
|
97
|
+
dispatchEvent({eventName: EVENTS.GA4_INIT_EVENT_SENT, detail: {eventName, sessionId}})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Clean old GA sessions hits from the storage.
|
|
101
|
+
Object.keys(localStorage).forEach(key => {
|
|
102
|
+
if (key.startsWith(eventPrefix) && key !== eventKey) {
|
|
103
|
+
localStorage.removeItem(key)
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const getGoogleField = async field => {
|
|
109
|
+
const googleAnalyticsMeasurementId = getConfig('googleAnalyticsMeasurementId')
|
|
110
|
+
|
|
111
|
+
// If `googleAnalyticsMeasurementId` is not present, don't load anything.
|
|
112
|
+
if (!googleAnalyticsMeasurementId) return Promise.resolve()
|
|
113
|
+
|
|
114
|
+
return new Promise(resolve => {
|
|
115
|
+
// If it is, get it from `gtag`.
|
|
116
|
+
window.gtag?.('get', googleAnalyticsMeasurementId, field, resolve)
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const trackingTagsTypes = {
|
|
121
|
+
STC: 'stc',
|
|
122
|
+
UTM: 'utm'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const getCampaignDetails = ({needsTransformation = true} = {}) => {
|
|
126
|
+
const {medium, source, campaign, content, term} = readQueryParams()
|
|
127
|
+
|
|
128
|
+
if (!medium || !source || !campaign) return null
|
|
129
|
+
|
|
130
|
+
const [id, name] = campaign.split(STC.CAMPAIGN_SPLIT_SYMBOL)
|
|
131
|
+
|
|
132
|
+
if (!id && !name) return null
|
|
133
|
+
|
|
134
|
+
const needsContent = typeof content !== 'undefined' && content !== STC_INVALID_CONTENT
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
campaign: {
|
|
138
|
+
medium: (needsTransformation && STC_MEDIUM_TRANSFORMATIONS[medium]) || medium,
|
|
139
|
+
...(typeof name !== 'undefined' && {id}),
|
|
140
|
+
name: name ?? id,
|
|
141
|
+
source,
|
|
142
|
+
...(needsContent && {content}),
|
|
143
|
+
...(typeof term !== 'undefined' && {term})
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function readQueryParams() {
|
|
149
|
+
const search = utils.getActualQueryString()
|
|
150
|
+
const searchParams = new URLSearchParams(search)
|
|
151
|
+
|
|
152
|
+
return getConfig('trackingTagsType') === trackingTagsTypes.UTM ? readFromUtm(searchParams) : readFromStc(searchParams)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function readFromStc(searchParams) {
|
|
156
|
+
if (!hasStc(searchParams)) return EMPTY_STC
|
|
157
|
+
|
|
158
|
+
const stc = searchParams.get(STC.QUERY)
|
|
159
|
+
const [medium, source, campaign, content, term] = stc.split(STC.SPLIT_SYMBOL)
|
|
160
|
+
return {medium, source, campaign, content, term}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function hasStc(searchParams) {
|
|
164
|
+
return searchParams.has(STC.QUERY)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function readFromUtm(searchParams) {
|
|
168
|
+
const campaignId = searchParams.get(UTM_CAMPAIGN_ID)
|
|
169
|
+
const campaignName = searchParams.get(UTM_CAMPAIGN)
|
|
170
|
+
const medium = searchParams.get(UTM_MEDIUM)
|
|
171
|
+
const source = searchParams.get(UTM_SOURCE)
|
|
172
|
+
const content = searchParams.get(UTM_CONTENT)
|
|
173
|
+
const term = searchParams.get(UTM_TERM)
|
|
174
|
+
const campaign = campaignId ? `${campaignId}${STC.CAMPAIGN_SPLIT_SYMBOL}${campaignName}` : campaignName
|
|
175
|
+
|
|
176
|
+
// Fallback to STC if any mandatory UTM fields are missing
|
|
177
|
+
const missingAnyMandatoryFields = !medium || !source || !campaign
|
|
178
|
+
if (missingAnyMandatoryFields && hasStc(searchParams)) {
|
|
179
|
+
return readFromStc(searchParams)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
campaign,
|
|
184
|
+
medium,
|
|
185
|
+
source,
|
|
186
|
+
content,
|
|
187
|
+
term
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export const getGoogleClientId = async () => getGoogleField(FIELDS.clientId)
|
|
192
|
+
export const getGoogleSessionId = async () => {
|
|
193
|
+
const sessionId = await getGoogleField(FIELDS.sessionId)
|
|
194
|
+
|
|
195
|
+
triggerGoogleAnalyticsInitEvent(sessionId)
|
|
196
|
+
|
|
197
|
+
return sessionId
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Unified consent state getter.
|
|
201
|
+
// Returns GRANTED, DENIED or undefined (default / unknown / unavailable).
|
|
202
|
+
export function getGoogleConsentValue(consentType = 'analytics_storage') {
|
|
203
|
+
try {
|
|
204
|
+
const value = window.google_tag_data?.ics?.getConsentState?.(consentType)
|
|
205
|
+
|
|
206
|
+
if (value === CONSENT_STATE_GRANTED_VALUE) return CONSENT_STATES.granted
|
|
207
|
+
if (value === CONSENT_STATE_DENIED_VALUE) return CONSENT_STATES.denied
|
|
208
|
+
|
|
209
|
+
return undefined
|
|
210
|
+
} catch (error) {
|
|
211
|
+
// GTM bug when first rejection happens: keep default undefined instead of forcing DENIED
|
|
212
|
+
return undefined
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Backwards compatibility alias (previous getConsentState behavior now unified but default is undefined)
|
|
217
|
+
export const getConsentState = () => getGoogleConsentValue() ?? CONSENT_STATES.denied
|
|
218
|
+
|
|
219
|
+
export const setGoogleUserId = userId => {
|
|
220
|
+
const googleAnalyticsMeasurementId = getConfig('googleAnalyticsMeasurementId')
|
|
221
|
+
|
|
222
|
+
if (!googleAnalyticsMeasurementId || !userId) return
|
|
223
|
+
|
|
224
|
+
window.gtag?.('set', 'user_id', userId)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Send consents to Google Consent Mode.
|
|
229
|
+
*
|
|
230
|
+
* @param {'default' | 'update'} mode Mode for the consent update
|
|
231
|
+
* @param {object} consents Consents object to be sent to Google Consent Mode.
|
|
232
|
+
* Defaults used when not provided:
|
|
233
|
+
* {
|
|
234
|
+
* analytics_storage: 'denied',
|
|
235
|
+
* ad_user_data: 'denied',
|
|
236
|
+
* ad_personalization: 'denied',
|
|
237
|
+
* ad_storage: 'denied'
|
|
238
|
+
* }
|
|
239
|
+
*/
|
|
240
|
+
export const sendGoogleConsents = (mode = 'default', consents) => {
|
|
241
|
+
window.gtag?.(
|
|
242
|
+
'consent',
|
|
243
|
+
mode,
|
|
244
|
+
consents || {
|
|
245
|
+
analytics_storage: CONSENT_STATES.denied,
|
|
246
|
+
ad_user_data: CONSENT_STATES.denied,
|
|
247
|
+
ad_personalization: CONSENT_STATES.denied,
|
|
248
|
+
ad_storage: CONSENT_STATES.denied
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {readCookie, removeCookie, saveCookie} from '../utils/cookies.js'
|
|
2
|
+
import {isClient, setConfig} from '../config.js'
|
|
3
|
+
import {USER_GDPR} from '../tcf.js'
|
|
4
|
+
|
|
5
|
+
const XANDR_ID_SERVER_URL = 'https://secure.adnxs.com/getuidj?consent=1'
|
|
6
|
+
const XANDR_ID_COOKIE = 'adit-xandr-id'
|
|
7
|
+
|
|
8
|
+
const USER_OPTED_OUT_XANDR_ID_VALUE = 0
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* [Xandr API Docs]{@link https://docs.xandr.com/bundle/invest_invest-standard/page/topics/user-id-mapping-with-getuid-and-mapuid.html}
|
|
12
|
+
* @returns {String|null} a valid xandrId value
|
|
13
|
+
*/
|
|
14
|
+
export function getXandrId({gdprPrivacyValueAdvertising}) {
|
|
15
|
+
if (!isClient) return null
|
|
16
|
+
|
|
17
|
+
if (gdprPrivacyValueAdvertising !== USER_GDPR.ACCEPTED) {
|
|
18
|
+
removeCookie(XANDR_ID_COOKIE)
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 0 is invalid. Negative numbers seems to be invalid too.
|
|
23
|
+
const checkValid = xandrId => xandrId && Number(xandrId) > USER_OPTED_OUT_XANDR_ID_VALUE
|
|
24
|
+
|
|
25
|
+
const storedXandrId = readCookie(XANDR_ID_COOKIE)
|
|
26
|
+
const isValidXandrId = checkValid(storedXandrId)
|
|
27
|
+
|
|
28
|
+
if (!isValidXandrId) {
|
|
29
|
+
getRemoteXandrId().then(xandrId => {
|
|
30
|
+
if (typeof xandrId === 'string') {
|
|
31
|
+
saveCookie(XANDR_ID_COOKIE, xandrId)
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
setConfig('xandrId', storedXandrId)
|
|
36
|
+
return isValidXandrId ? storedXandrId : null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getRemoteXandrId() {
|
|
40
|
+
if (!isClient) return Promise.resolve(null)
|
|
41
|
+
|
|
42
|
+
return window
|
|
43
|
+
.fetch(XANDR_ID_SERVER_URL, {
|
|
44
|
+
credentials: 'include'
|
|
45
|
+
})
|
|
46
|
+
.then(response => response.json())
|
|
47
|
+
.then(json => json?.uid)
|
|
48
|
+
}
|