@el7ven/cookie-kit 0.3.1 → 0.3.2
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/package.json +21 -1
- package/src/core/AnalyticsManager.js +400 -0
- package/src/core/ConsentManager.js +710 -0
- package/src/core/ConsentMode.js +109 -0
- package/src/core/FocusTrap.js +130 -0
- package/src/core/GeoDetector.js +144 -0
- package/src/core/ScriptLoader.js +229 -0
- package/src/core/StorageAdapter.js +179 -0
- package/src/geo/GeoDetector.js +536 -0
- package/src/geo/RegionRules.js +126 -0
- package/src/geo/index.js +16 -0
- package/src/index.js +55 -17
- package/src/locales/en.js +54 -0
- package/src/locales/index.js +20 -0
- package/src/locales/ro.js +54 -0
- package/src/plugins/CMPPlugin.js +187 -0
- package/src/plugins/PluginManager.js +234 -0
- package/src/plugins/index.js +7 -0
- package/src/providers/GoogleConsentModeProvider.js +278 -0
- package/src/providers/index.js +6 -0
- package/src/rewriting/ScriptRewriter.js +278 -0
- package/src/rewriting/index.js +6 -0
- package/src/scripts/ScriptLoader.js +310 -0
- package/src/scripts/ScriptManager.js +278 -0
- package/src/scripts/ScriptRegistry.js +175 -0
- package/src/scripts/ScriptScanner.js +178 -0
- package/src/scripts/index.js +9 -0
- package/src/trackers/TrackerDetector.js +488 -0
- package/src/trackers/TrackerPatterns.js +307 -0
- package/src/trackers/TrackerRegistry.js +172 -0
- package/src/trackers/index.js +15 -0
- package/src/utils/cookies.js +37 -0
- package/src/utils/dom.js +58 -0
- package/src/utils/helpers.js +89 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Manager
|
|
3
|
+
* @module PluginManager
|
|
4
|
+
*
|
|
5
|
+
* Manages CMP plugins lifecycle and events
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class PluginManager {
|
|
9
|
+
constructor(cmpInstance) {
|
|
10
|
+
this.cmp = cmpInstance
|
|
11
|
+
this.plugins = new Map() // name -> plugin instance
|
|
12
|
+
this.hooks = {
|
|
13
|
+
beforeInit: [],
|
|
14
|
+
afterInit: [],
|
|
15
|
+
beforeBannerShow: [],
|
|
16
|
+
afterBannerShow: [],
|
|
17
|
+
beforeConsentSave: [],
|
|
18
|
+
afterConsentSave: []
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Register plugin
|
|
24
|
+
* @param {CMPPlugin} plugin - Plugin instance
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
*/
|
|
27
|
+
register(plugin) {
|
|
28
|
+
if (!plugin || !plugin.name) return false
|
|
29
|
+
if (this.plugins.has(plugin.name)) return false
|
|
30
|
+
|
|
31
|
+
// Initialize plugin
|
|
32
|
+
plugin.init(this.cmp)
|
|
33
|
+
|
|
34
|
+
// Store plugin
|
|
35
|
+
this.plugins.set(plugin.name, plugin)
|
|
36
|
+
|
|
37
|
+
// Register lifecycle hooks
|
|
38
|
+
this._registerHooks(plugin)
|
|
39
|
+
|
|
40
|
+
// Setup event listeners
|
|
41
|
+
this._setupEventListeners(plugin)
|
|
42
|
+
|
|
43
|
+
this._debug(`Plugin "${plugin.name}" registered`)
|
|
44
|
+
return true
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Unregister plugin
|
|
49
|
+
* @param {string} pluginName
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
unregister(pluginName) {
|
|
53
|
+
const plugin = this.plugins.get(pluginName)
|
|
54
|
+
if (!plugin) {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Cleanup plugin
|
|
59
|
+
plugin.destroy()
|
|
60
|
+
|
|
61
|
+
// Remove from hooks
|
|
62
|
+
this._unregisterHooks(plugin)
|
|
63
|
+
|
|
64
|
+
// Remove plugin
|
|
65
|
+
this.plugins.delete(pluginName)
|
|
66
|
+
|
|
67
|
+
this._debug(`Plugin "${pluginName}" unregistered`)
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get plugin by name
|
|
73
|
+
* @param {string} pluginName
|
|
74
|
+
* @returns {CMPPlugin|null}
|
|
75
|
+
*/
|
|
76
|
+
get(pluginName) {
|
|
77
|
+
return this.plugins.get(pluginName) || null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get all plugins
|
|
82
|
+
* @returns {Array}
|
|
83
|
+
*/
|
|
84
|
+
getAll() {
|
|
85
|
+
return Array.from(this.plugins.values())
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if plugin is registered
|
|
90
|
+
* @param {string} pluginName
|
|
91
|
+
* @returns {boolean}
|
|
92
|
+
*/
|
|
93
|
+
has(pluginName) {
|
|
94
|
+
return this.plugins.has(pluginName)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Execute lifecycle hook
|
|
99
|
+
* @param {string} hookName
|
|
100
|
+
* @param {*} data
|
|
101
|
+
* @returns {*}
|
|
102
|
+
*/
|
|
103
|
+
async executeHook(hookName, data = null) {
|
|
104
|
+
const hooks = this.hooks[hookName] || []
|
|
105
|
+
|
|
106
|
+
let result = data
|
|
107
|
+
|
|
108
|
+
for (const hook of hooks) {
|
|
109
|
+
try {
|
|
110
|
+
const hookResult = await hook(result)
|
|
111
|
+
// For beforeConsentSave, allow modification
|
|
112
|
+
if (hookName === 'beforeConsentSave' && hookResult !== undefined) {
|
|
113
|
+
result = hookResult
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
// Silent error
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Register plugin hooks
|
|
125
|
+
* @private
|
|
126
|
+
*/
|
|
127
|
+
_registerHooks(plugin) {
|
|
128
|
+
// beforeInit
|
|
129
|
+
if (typeof plugin.beforeInit === 'function') {
|
|
130
|
+
this.hooks.beforeInit.push(() => plugin.beforeInit())
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// afterInit
|
|
134
|
+
if (typeof plugin.afterInit === 'function') {
|
|
135
|
+
this.hooks.afterInit.push(() => plugin.afterInit())
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// beforeBannerShow
|
|
139
|
+
if (typeof plugin.beforeBannerShow === 'function') {
|
|
140
|
+
this.hooks.beforeBannerShow.push(() => plugin.beforeBannerShow())
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// afterBannerShow
|
|
144
|
+
if (typeof plugin.afterBannerShow === 'function') {
|
|
145
|
+
this.hooks.afterBannerShow.push(() => plugin.afterBannerShow())
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// beforeConsentSave
|
|
149
|
+
if (typeof plugin.beforeConsentSave === 'function') {
|
|
150
|
+
this.hooks.beforeConsentSave.push((consent) => plugin.beforeConsentSave(consent))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// afterConsentSave
|
|
154
|
+
if (typeof plugin.afterConsentSave === 'function') {
|
|
155
|
+
this.hooks.afterConsentSave.push((consent) => plugin.afterConsentSave(consent))
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Unregister plugin hooks
|
|
161
|
+
* @private
|
|
162
|
+
*/
|
|
163
|
+
_unregisterHooks(plugin) {
|
|
164
|
+
Object.keys(this.hooks).forEach(hookName => {
|
|
165
|
+
this.hooks[hookName] = this.hooks[hookName].filter(hook => {
|
|
166
|
+
// Remove hooks that belong to this plugin
|
|
167
|
+
return hook.toString().indexOf(plugin.name) === -1
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Setup event listeners for plugin
|
|
174
|
+
* @private
|
|
175
|
+
*/
|
|
176
|
+
_setupEventListeners(plugin) {
|
|
177
|
+
// Consent changed
|
|
178
|
+
if (typeof plugin.onConsentChange === 'function') {
|
|
179
|
+
this.cmp.on('consentChanged', (data) => plugin.onConsentChange(data))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Consent accepted
|
|
183
|
+
if (typeof plugin.onConsentAccepted === 'function') {
|
|
184
|
+
this.cmp.on('consentAccepted', (data) => plugin.onConsentAccepted(data))
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Consent rejected
|
|
188
|
+
if (typeof plugin.onConsentRejected === 'function') {
|
|
189
|
+
this.cmp.on('consentRejected', (data) => plugin.onConsentRejected(data))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Consent expired
|
|
193
|
+
if (typeof plugin.onConsentExpired === 'function') {
|
|
194
|
+
this.cmp.on('consentExpired', () => plugin.onConsentExpired())
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Script loaded
|
|
198
|
+
if (typeof plugin.onScriptLoaded === 'function') {
|
|
199
|
+
if (typeof window !== 'undefined') {
|
|
200
|
+
window.addEventListener('cookie-consent:script-loaded', (e) => {
|
|
201
|
+
plugin.onScriptLoaded(e.detail)
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Tracker detected
|
|
207
|
+
if (typeof plugin.onTrackerDetected === 'function') {
|
|
208
|
+
if (typeof window !== 'undefined') {
|
|
209
|
+
window.addEventListener('cookie-consent:tracker-detected', (e) => {
|
|
210
|
+
plugin.onTrackerDetected(e.detail)
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Get plugin stats
|
|
218
|
+
* @returns {Object}
|
|
219
|
+
*/
|
|
220
|
+
getStats() {
|
|
221
|
+
return {
|
|
222
|
+
total: this.plugins.size,
|
|
223
|
+
plugins: this.getAll().map(p => ({
|
|
224
|
+
name: p.name,
|
|
225
|
+
version: p.version,
|
|
226
|
+
initialized: p.isInitialized
|
|
227
|
+
}))
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
_debug(...args) {
|
|
232
|
+
// Debug disabled
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Consent Mode v2 Provider
|
|
3
|
+
* Required for Google Ads, GA4, and GTM compliance
|
|
4
|
+
* @module GoogleConsentModeProvider
|
|
5
|
+
* @see https://developers.google.com/tag-platform/security/guides/consent
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class GoogleConsentModeProvider {
|
|
9
|
+
constructor(consentManager, config = {}) {
|
|
10
|
+
this.consentManager = consentManager
|
|
11
|
+
this.config = {
|
|
12
|
+
// Category mapping: CMP category → Google consent type
|
|
13
|
+
categoryMapping: {
|
|
14
|
+
analytics: ['analytics_storage'],
|
|
15
|
+
marketing: ['ad_storage', 'ad_user_data', 'ad_personalization'],
|
|
16
|
+
preferences: ['functionality_storage', 'personalization_storage'],
|
|
17
|
+
...config.categoryMapping
|
|
18
|
+
},
|
|
19
|
+
// Wait for GTM/GA to load
|
|
20
|
+
waitForGtag: true,
|
|
21
|
+
// Region-specific settings
|
|
22
|
+
regionSettings: {
|
|
23
|
+
EU: { wait_for_update: 500 },
|
|
24
|
+
UK: { wait_for_update: 500 },
|
|
25
|
+
US: { wait_for_update: 0 },
|
|
26
|
+
...config.regionSettings
|
|
27
|
+
},
|
|
28
|
+
// Enable URL passthrough for conversion tracking
|
|
29
|
+
urlPassthrough: true,
|
|
30
|
+
// Enable ads data redaction
|
|
31
|
+
adsDataRedaction: true,
|
|
32
|
+
...config
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.isInitialized = false
|
|
36
|
+
this.lastConsentHash = null // Prevent duplicate updates
|
|
37
|
+
this._debug('GoogleConsentModeProvider created')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Initialize Google Consent Mode
|
|
42
|
+
* MUST be called before GA/GTM loads
|
|
43
|
+
*/
|
|
44
|
+
initialize() {
|
|
45
|
+
if (this.isInitialized) {
|
|
46
|
+
this._debug('Already initialized')
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Bootstrap gtag (CRITICAL: before GA/GTM loads)
|
|
51
|
+
this._bootstrapGtag()
|
|
52
|
+
|
|
53
|
+
// Set default consent state (DENIED for all)
|
|
54
|
+
this.setDefaultConsent()
|
|
55
|
+
|
|
56
|
+
// Listen for consent changes
|
|
57
|
+
this.consentManager.on('consentChanged', ({ consent, categories }) => {
|
|
58
|
+
this.updateConsent(categories, consent.region)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Apply existing consent if available
|
|
62
|
+
const existingConsent = this.consentManager.getConsent()
|
|
63
|
+
if (existingConsent) {
|
|
64
|
+
this.updateConsent(existingConsent.categories, existingConsent.region)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.isInitialized = true
|
|
68
|
+
this._debug('Google Consent Mode v2 initialized')
|
|
69
|
+
this._emitEvent('gcm:initialized', {})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Set default consent state (all DENIED)
|
|
74
|
+
* Called before GA/GTM loads
|
|
75
|
+
*/
|
|
76
|
+
setDefaultConsent() {
|
|
77
|
+
const region = this.consentManager.getConsent()?.region || 'ROW'
|
|
78
|
+
const regionConfig = this.config.regionSettings[region] || {}
|
|
79
|
+
|
|
80
|
+
const defaultConsent = {
|
|
81
|
+
ad_storage: 'denied',
|
|
82
|
+
analytics_storage: 'denied',
|
|
83
|
+
ad_user_data: 'denied',
|
|
84
|
+
ad_personalization: 'denied',
|
|
85
|
+
functionality_storage: 'denied',
|
|
86
|
+
personalization_storage: 'denied',
|
|
87
|
+
security_storage: 'granted', // Always granted for security
|
|
88
|
+
|
|
89
|
+
// CRITICAL: wait_for_update gives CMP time to update consent
|
|
90
|
+
// Prevents GA from firing before consent update
|
|
91
|
+
wait_for_update: regionConfig.wait_for_update !== undefined
|
|
92
|
+
? regionConfig.wait_for_update
|
|
93
|
+
: 500, // Default 500ms for all regions
|
|
94
|
+
|
|
95
|
+
...regionConfig
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// URL passthrough for conversion tracking without cookies
|
|
99
|
+
if (this.config.urlPassthrough) {
|
|
100
|
+
defaultConsent.url_passthrough = true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Redact ads data until consent
|
|
104
|
+
if (this.config.adsDataRedaction) {
|
|
105
|
+
defaultConsent.ads_data_redaction = true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this._debug('Setting default consent:', defaultConsent)
|
|
109
|
+
|
|
110
|
+
window.gtag('consent', 'default', defaultConsent)
|
|
111
|
+
|
|
112
|
+
this._emitEvent('gcm:default-set', { consent: defaultConsent })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Update consent based on user choice
|
|
117
|
+
* @param {Object} categories - Consent categories
|
|
118
|
+
* @param {string} region - User region
|
|
119
|
+
*/
|
|
120
|
+
updateConsent(categories, region = 'ROW') {
|
|
121
|
+
const consentUpdate = this._mapCategoriesToGoogleConsent(categories)
|
|
122
|
+
|
|
123
|
+
// Deduplication: prevent duplicate updates
|
|
124
|
+
const consentHash = this._hashConsent(consentUpdate)
|
|
125
|
+
if (this.lastConsentHash === consentHash) {
|
|
126
|
+
this._debug('Consent unchanged, skipping update')
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
this.lastConsentHash = consentHash
|
|
130
|
+
|
|
131
|
+
this._debug('Updating consent:', consentUpdate)
|
|
132
|
+
|
|
133
|
+
window.gtag('consent', 'update', consentUpdate)
|
|
134
|
+
|
|
135
|
+
this._emitEvent('gcm:updated', {
|
|
136
|
+
consent: consentUpdate,
|
|
137
|
+
categories,
|
|
138
|
+
region
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Map CMP categories to Google consent types
|
|
144
|
+
* @param {Object} categories - CMP categories
|
|
145
|
+
* @returns {Object} Google consent object
|
|
146
|
+
* @private
|
|
147
|
+
*/
|
|
148
|
+
_mapCategoriesToGoogleConsent(categories) {
|
|
149
|
+
const googleConsent = {}
|
|
150
|
+
|
|
151
|
+
// Map each CMP category to Google consent types
|
|
152
|
+
Object.entries(this.config.categoryMapping).forEach(([cmpCategory, googleTypes]) => {
|
|
153
|
+
const isGranted = categories[cmpCategory] === true
|
|
154
|
+
const consentValue = isGranted ? 'granted' : 'denied'
|
|
155
|
+
|
|
156
|
+
googleTypes.forEach(googleType => {
|
|
157
|
+
googleConsent[googleType] = consentValue
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// Security storage always granted
|
|
162
|
+
googleConsent.security_storage = 'granted'
|
|
163
|
+
|
|
164
|
+
return googleConsent
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get current consent state
|
|
169
|
+
* @returns {Object}
|
|
170
|
+
*/
|
|
171
|
+
getConsentState() {
|
|
172
|
+
const consent = this.consentManager.getConsent()
|
|
173
|
+
if (!consent) {
|
|
174
|
+
return {
|
|
175
|
+
ad_storage: 'denied',
|
|
176
|
+
analytics_storage: 'denied',
|
|
177
|
+
ad_user_data: 'denied',
|
|
178
|
+
ad_personalization: 'denied'
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return this._mapCategoriesToGoogleConsent(consent.categories)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check if specific Google consent type is granted
|
|
187
|
+
* @param {string} consentType - Google consent type
|
|
188
|
+
* @returns {boolean}
|
|
189
|
+
*/
|
|
190
|
+
isGranted(consentType) {
|
|
191
|
+
const state = this.getConsentState()
|
|
192
|
+
return state[consentType] === 'granted'
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Manually trigger consent update
|
|
197
|
+
* Useful for testing or manual control
|
|
198
|
+
*/
|
|
199
|
+
refresh() {
|
|
200
|
+
const consent = this.consentManager.getConsent()
|
|
201
|
+
if (consent) {
|
|
202
|
+
this.updateConsent(consent.categories, consent.region)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get category mapping configuration
|
|
208
|
+
* @returns {Object}
|
|
209
|
+
*/
|
|
210
|
+
getCategoryMapping() {
|
|
211
|
+
return { ...this.config.categoryMapping }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Update category mapping
|
|
216
|
+
* @param {Object} mapping - New mapping
|
|
217
|
+
*/
|
|
218
|
+
setCategoryMapping(mapping) {
|
|
219
|
+
this.config.categoryMapping = { ...this.config.categoryMapping, ...mapping }
|
|
220
|
+
this.refresh()
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// --- Internal helpers ---
|
|
224
|
+
|
|
225
|
+
_emitEvent(eventName, detail) {
|
|
226
|
+
if (typeof window !== 'undefined') {
|
|
227
|
+
window.dispatchEvent(new CustomEvent(`cookie-consent:${eventName}`, { detail }))
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Bootstrap gtag function
|
|
233
|
+
* MUST run before GA/GTM loads
|
|
234
|
+
* @private
|
|
235
|
+
*/
|
|
236
|
+
_bootstrapGtag() {
|
|
237
|
+
// Initialize dataLayer
|
|
238
|
+
window.dataLayer = window.dataLayer || []
|
|
239
|
+
|
|
240
|
+
// Create gtag function if not exists
|
|
241
|
+
if (!window.gtag) {
|
|
242
|
+
window.gtag = function() {
|
|
243
|
+
window.dataLayer.push(arguments)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Mark gtag as bootstrapped by CMP
|
|
247
|
+
window.gtag.l = Date.now()
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
this._debug('gtag bootstrapped')
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Hash consent object for deduplication
|
|
255
|
+
* @private
|
|
256
|
+
*/
|
|
257
|
+
_hashConsent(consent) {
|
|
258
|
+
return JSON.stringify(consent)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
_debug(...args) {
|
|
262
|
+
if (this.consentManager.config.debug) {
|
|
263
|
+
console.log('[GoogleConsentMode]', ...args)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Helper function to initialize Google Consent Mode
|
|
270
|
+
* @param {ConsentManager} consentManager - Consent manager instance
|
|
271
|
+
* @param {Object} config - Configuration
|
|
272
|
+
* @returns {GoogleConsentModeProvider}
|
|
273
|
+
*/
|
|
274
|
+
export function initGoogleConsentMode(consentManager, config = {}) {
|
|
275
|
+
const provider = new GoogleConsentModeProvider(consentManager, config)
|
|
276
|
+
provider.initialize()
|
|
277
|
+
return provider
|
|
278
|
+
}
|