@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@el7ven/cookie-kit",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "module": "./src/index.js",
@@ -13,6 +13,26 @@
13
13
  "./vue": "./src/vue/index.js",
14
14
  "./analytics": "./src/core/analytics.js",
15
15
  "./config": "./src/core/index.js",
16
+ "./core": "./src/core/index.js",
17
+ "./core/ConsentManager": "./src/core/ConsentManager.js",
18
+ "./core/StorageAdapter": "./src/core/StorageAdapter.js",
19
+ "./core/ConsentMode": "./src/core/ConsentMode.js",
20
+ "./core/FocusTrap": "./src/core/FocusTrap.js",
21
+ "./core/ScriptLoader": "./src/core/ScriptLoader.js",
22
+ "./core/AnalyticsManager": "./src/core/AnalyticsManager.js",
23
+ "./core/GeoDetector": "./src/core/GeoDetector.js",
24
+ "./geo": "./src/geo/index.js",
25
+ "./geo/RegionRules": "./src/geo/RegionRules.js",
26
+ "./locales": "./src/locales/index.js",
27
+ "./providers": "./src/providers/index.js",
28
+ "./providers/GoogleConsentModeProvider": "./src/providers/GoogleConsentModeProvider.js",
29
+ "./plugins": "./src/plugins/index.js",
30
+ "./trackers": "./src/trackers/index.js",
31
+ "./rewriting": "./src/rewriting/index.js",
32
+ "./scripts": "./src/scripts/index.js",
33
+ "./utils/cookies": "./src/utils/cookies.js",
34
+ "./utils/dom": "./src/utils/dom.js",
35
+ "./utils/helpers": "./src/utils/helpers.js",
16
36
  "./composables/useCookieConsent": "./src/vue/composables/useCookieConsent.js",
17
37
  "./src/composables/useCookieConsent": "./src/vue/composables/useCookieConsent.js",
18
38
  "./package.json": "./package.json"
@@ -0,0 +1,400 @@
1
+ /**
2
+ * Analytics Manager — integrations with popular analytics services
3
+ * GA4, GTM, Meta Pixel, TikTok Pixel, Hotjar
4
+ * @module AnalyticsManager
5
+ */
6
+
7
+ const ANALYTICS_PROVIDERS = {
8
+ ga4: {
9
+ category: 'analytics',
10
+ load: (config) => {
11
+ const id = config.GA_ID || config.id
12
+ if (!id) return
13
+
14
+ // gtag.js is already loaded in head.blade.php, just configure
15
+ if (window.gtag) {
16
+ // Google Consent Mode V2 - update consent to granted
17
+ gtag('consent', 'update', {
18
+ 'analytics_storage': 'granted',
19
+ 'ad_storage': 'denied',
20
+ 'ad_user_data': 'denied',
21
+ 'ad_personalization': 'denied'
22
+ })
23
+
24
+ // Configure GA4 with IP anonymization (GDPR requirement)
25
+ gtag('config', id, {
26
+ anonymize_ip: true
27
+ })
28
+ }
29
+
30
+ return true // Return success flag
31
+ },
32
+ unload: (config) => {
33
+ const id = config.GA_ID || config.id
34
+
35
+ // Update consent to denied
36
+ if (window.gtag) {
37
+ gtag('consent', 'update', {
38
+ 'analytics_storage': 'denied',
39
+ 'ad_storage': 'denied',
40
+ 'ad_user_data': 'denied',
41
+ 'ad_personalization': 'denied'
42
+ })
43
+ }
44
+
45
+ // Remove ALL GA cookies (including _ga_XXXX and _gat)
46
+ const cookies = document.cookie.split(';')
47
+ cookies.forEach(cookie => {
48
+ const name = cookie.split('=')[0].trim()
49
+ if (name.startsWith('_ga') || name.startsWith('_gid') || name.startsWith('_gat')) {
50
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`
51
+ }
52
+ })
53
+ }
54
+ },
55
+
56
+ gtm: {
57
+ category: 'analytics',
58
+ load: (config) => {
59
+ const id = config.GTM_ID || config.id
60
+ if (!id) return
61
+
62
+ window.dataLayer = window.dataLayer || []
63
+
64
+ // Google Consent Mode V2 - set consent to granted
65
+ window.dataLayer.push({
66
+ 'event': 'consent_update',
67
+ 'analytics_storage': 'granted',
68
+ 'ad_storage': 'denied',
69
+ 'ad_user_data': 'denied',
70
+ 'ad_personalization': 'denied'
71
+ })
72
+
73
+ window.dataLayer.push({
74
+ 'gtm.start': new Date().getTime(),
75
+ event: 'gtm.js'
76
+ })
77
+
78
+ const script = document.createElement('script')
79
+ script.async = true
80
+ script.src = `https://www.googletagmanager.com/gtm.js?id=${id}`
81
+ document.head.appendChild(script)
82
+
83
+ return script
84
+ },
85
+ unload: (config) => {
86
+ const id = config.GTM_ID || config.id
87
+
88
+ // Google Consent Mode V2 - set consent to denied
89
+ if (window.dataLayer) {
90
+ window.dataLayer.push({
91
+ 'event': 'consent_update',
92
+ 'analytics_storage': 'denied',
93
+ 'ad_storage': 'denied',
94
+ 'ad_user_data': 'denied',
95
+ 'ad_personalization': 'denied'
96
+ })
97
+ }
98
+
99
+ const scripts = document.querySelectorAll(`script[src*="googletagmanager.com/gtm.js?id=${id}"]`)
100
+ scripts.forEach(s => s.remove())
101
+ }
102
+ },
103
+
104
+ metaPixel: {
105
+ category: 'marketing',
106
+ load: (config) => {
107
+ const id = config.PIXEL_ID || config.id
108
+ if (!id) return
109
+
110
+ /* eslint-disable */
111
+ !function(f,b,e,v,n,t,s)
112
+ {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
113
+ n.callMethod.apply(n,arguments):n.queue.push(arguments)};
114
+ if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
115
+ n.queue=[];t=b.createElement(e);t.async=!0;
116
+ t.src=v;s=b.getElementsByTagName(e)[0];
117
+ s.parentNode.insertBefore(t,s)}(window, document,'script',
118
+ 'https://connect.facebook.net/en_US/fbevents.js');
119
+ /* eslint-enable */
120
+
121
+ window.fbq('init', id)
122
+ window.fbq('track', 'PageView')
123
+ },
124
+ unload: () => {
125
+ delete window.fbq
126
+ delete window._fbq
127
+ const scripts = document.querySelectorAll('script[src*="connect.facebook.net"]')
128
+ scripts.forEach(s => s.remove())
129
+ }
130
+ },
131
+
132
+ yandexMetrica: {
133
+ category: 'analytics',
134
+ load: (config) => {
135
+ const id = config.YANDEX_ID || config.id
136
+ console.log('[AnalyticsManager] Yandex Metrica: Attempting to load with ID:', id)
137
+ if (!id) {
138
+ console.log('[AnalyticsManager] Yandex Metrica: No ID provided, skipping')
139
+ return
140
+ }
141
+
142
+ // Yandex.Metrika initialization with proper parameters
143
+ (function(m,e,t,r,i,k,a){
144
+ m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
145
+ m[i].l=1*new Date();
146
+ for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
147
+ k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
148
+ })(window, document,'script','https://mc.yandex.ru/metrika/tag.js?id=' + id, 'ym');
149
+
150
+ ym(id, 'init', {
151
+ ssr: true,
152
+ webvisor: true,
153
+ clickmap: true,
154
+ ecommerce: "dataLayer",
155
+ referrer: document.referrer,
156
+ url: location.href,
157
+ accurateTrackBounce: true,
158
+ trackLinks: true
159
+ });
160
+ console.log('[AnalyticsManager] Yandex Metrica: Initialized with ID:', id)
161
+
162
+ return id
163
+ },
164
+ unload: (config) => {
165
+ const id = config.YANDEX_ID || config.id
166
+ console.log('[AnalyticsManager] Yandex Metrica: Unloading with ID:', id)
167
+
168
+ // Remove Yandex cookies
169
+ const cookies = document.cookie.split(';')
170
+ let removedCookies = []
171
+ cookies.forEach(cookie => {
172
+ const name = cookie.split('=')[0].trim()
173
+ if (name.includes('yandex') || name.includes('ym') || name.includes('_ym')) {
174
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`
175
+ removedCookies.push(name)
176
+ }
177
+ })
178
+ console.log('[AnalyticsManager] Yandex Metrica: Removed cookies:', removedCookies)
179
+
180
+ // Remove script
181
+ const scripts = document.querySelectorAll('script[src*="yandex.ru/metrika"]')
182
+ scripts.forEach(s => s.remove())
183
+ console.log('[AnalyticsManager] Yandex Metrica: Removed scripts:', scripts.length)
184
+
185
+ // Remove noscript image
186
+ const noscriptImgs = document.querySelectorAll('img[src*="yandex.ru/watch"]')
187
+ noscriptImgs.forEach(img => img.remove())
188
+ console.log('[AnalyticsManager] Yandex Metrica: Removed noscript images:', noscriptImgs.length)
189
+ }
190
+ },
191
+
192
+ googleRemarketing: {
193
+ category: 'marketing',
194
+ load: (config) => {
195
+ const id = config.REMARKETING_ID || config.id
196
+ console.log('[AnalyticsManager] Google Remarketing: Attempting to load with ID:', id)
197
+ if (!id) {
198
+ console.log('[AnalyticsManager] Google Remarketing: No ID provided, skipping')
199
+ return
200
+ }
201
+
202
+ window.google_tag_params = window.google_tag_params || {};
203
+ window.google_conversion_id = id;
204
+ window.google_conversion_label = '';
205
+ window.google_remarketing_only = true;
206
+
207
+ const script = document.createElement('script')
208
+ script.src = `//www.googleadservices.com/pagead/conversion.js`
209
+ document.head.appendChild(script)
210
+ console.log('[AnalyticsManager] Google Remarketing: Script appended to head')
211
+
212
+ return script
213
+ },
214
+ unload: (config) => {
215
+ const id = config.REMARKETING_ID || config.id
216
+ console.log('[AnalyticsManager] Google Remarketing: Unloading with ID:', id)
217
+
218
+ // Remove remarketing cookies
219
+ const cookies = document.cookie.split(';')
220
+ let removedCookies = []
221
+ cookies.forEach(cookie => {
222
+ const name = cookie.split('=')[0].trim()
223
+ if (name.includes('_ga') || name.includes('google')) {
224
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`
225
+ removedCookies.push(name)
226
+ }
227
+ })
228
+ console.log('[AnalyticsManager] Google Remarketing: Removed cookies:', removedCookies)
229
+
230
+ // Remove script
231
+ const scripts = document.querySelectorAll('script[src*="googleadservices.com"]')
232
+ scripts.forEach(s => s.remove())
233
+ console.log('[AnalyticsManager] Google Remarketing: Removed scripts:', scripts.length)
234
+ }
235
+ },
236
+
237
+ tiktokPixel: {
238
+ category: 'marketing',
239
+ load: (config) => {
240
+ const id = config.TIKTOK_ID || config.id
241
+ if (!id) return
242
+
243
+ /* eslint-disable */
244
+ !function(w,d,t){w.TiktokAnalyticsObject=t;var ttq=w[t]=w[t]||[];
245
+ ttq.methods=["page","track","identify","instances","debug","on","off","once","ready","alias","group","enableCookie","disableCookie"];
246
+ ttq.setAndDefer=function(t,e){t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}};
247
+ for(var i=0;i<ttq.methods.length;i++)ttq.setAndDefer(ttq,ttq.methods[i]);
248
+ ttq.instance=function(t){for(var e=ttq._i[t]||[],n=0;n<ttq.methods.length;n++)ttq.setAndDefer(e,ttq.methods[n]);return e};
249
+ ttq.load=function(e,n){var i="https://analytics.tiktok.com/i18n/pixel/events.js";
250
+ ttq._i=ttq._i||{};ttq._i[e]=[];ttq._i[e]._u=i;ttq._t=ttq._t||{};ttq._t[e]=+new Date;
251
+ ttq._o=ttq._o||{};ttq._o[e]=n||{};
252
+ var o=document.createElement("script");o.type="text/javascript";o.async=!0;o.src=i+"?sdkid="+e+"&lib="+t;
253
+ var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(o,a)};
254
+ }(window,document,"ttq");
255
+ /* eslint-enable */
256
+
257
+ window.ttq.load(id)
258
+ window.ttq.page()
259
+ },
260
+ unload: () => {
261
+ delete window.ttq
262
+ const scripts = document.querySelectorAll('script[src*="analytics.tiktok.com"]')
263
+ scripts.forEach(s => s.remove())
264
+ }
265
+ },
266
+
267
+ hotjar: {
268
+ category: 'analytics',
269
+ load: (config) => {
270
+ const id = config.HOTJAR_ID || config.id
271
+ if (!id) return
272
+
273
+ /* eslint-disable */
274
+ ;(function(h,o,t,j,a,r){
275
+ h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
276
+ h._hjSettings={hjid:id,hjsv:6};
277
+ a=o.getElementsByTagName('head')[0];
278
+ r=o.createElement('script');r.async=1;
279
+ r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
280
+ a.appendChild(r);
281
+ })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
282
+ /* eslint-enable */
283
+ },
284
+ unload: () => {
285
+ delete window.hj
286
+ delete window._hjSettings
287
+ const scripts = document.querySelectorAll('script[src*="static.hotjar.com"]')
288
+ scripts.forEach(s => s.remove())
289
+ }
290
+ }
291
+ }
292
+
293
+ export class AnalyticsManager {
294
+ constructor(consentManager, config = {}) {
295
+ this.manager = consentManager
296
+ this.config = config
297
+ this.activeProviders = new Set()
298
+
299
+ // Initialize Google Consent Mode V2 with default denied consent
300
+ this._initializeConsentMode()
301
+
302
+ this._init()
303
+ }
304
+
305
+ _initializeConsentMode() {
306
+ // Set default consent to denied (GDPR compliant) - only once
307
+ if (!window.gtag) {
308
+ window.dataLayer = window.dataLayer || []
309
+ window.gtag = function gtag() {
310
+ window.dataLayer.push(arguments)
311
+ }
312
+ }
313
+
314
+ // Set default consent to denied (GDPR compliant)
315
+ gtag('consent', 'default', {
316
+ 'analytics_storage': 'denied',
317
+ 'ad_storage': 'denied',
318
+ 'ad_user_data': 'denied',
319
+ 'ad_personalization': 'denied',
320
+ 'wait_for_update': 500 // Prevent race conditions
321
+ })
322
+ }
323
+
324
+ _init() {
325
+ if (typeof window === 'undefined') return
326
+
327
+ // Listen for consent changes
328
+ this.manager.on('consentChanged', ({ categories }) => {
329
+ this._syncProviders(categories)
330
+ })
331
+
332
+ // Check existing consent
333
+ const consent = this.manager.getConsent()
334
+ if (consent?.hasConsented) {
335
+ this._syncProviders(consent.categories)
336
+ }
337
+
338
+ this.manager._debug('AnalyticsManager: initialized')
339
+ }
340
+
341
+ _syncProviders(categories) {
342
+ // When using direct gtag.js, both GA4 and GTM can work together
343
+ const hasGTM = this.config.gtm && (this.config.gtm.id || this.config.gtm.GTM_ID)
344
+ const hasGA4 = this.config.ga4 && (this.config.ga4.id || this.config.ga4.GA_ID)
345
+
346
+ Object.entries(this.config).forEach(([providerName, providerConfig]) => {
347
+ const provider = ANALYTICS_PROVIDERS[providerName]
348
+ if (!provider || !providerConfig) return
349
+
350
+ const category = providerConfig.category || provider.category
351
+ const granted = categories[category] === true
352
+
353
+ if (granted && !this.activeProviders.has(providerName)) {
354
+ // Load provider
355
+ try {
356
+ provider.load(providerConfig)
357
+ this.activeProviders.add(providerName)
358
+ } catch (e) {
359
+ this.manager._debug('AnalyticsManager: load error', providerName, e)
360
+ }
361
+ } else if (!granted && this.activeProviders.has(providerName)) {
362
+ // Unload provider
363
+ try {
364
+ provider.unload(providerConfig)
365
+ this.activeProviders.delete(providerName)
366
+ } catch (e) {
367
+ this.manager._debug('AnalyticsManager: unload error', providerName, e)
368
+ }
369
+ }
370
+ })
371
+ }
372
+
373
+ /**
374
+ * Register a custom analytics provider
375
+ */
376
+ registerProvider(name, provider) {
377
+ ANALYTICS_PROVIDERS[name] = provider
378
+ this.manager._debug('AnalyticsManager: registered custom provider', name)
379
+ }
380
+
381
+ /**
382
+ * Get active providers
383
+ */
384
+ getActiveProviders() {
385
+ return [...this.activeProviders]
386
+ }
387
+
388
+ destroy() {
389
+ // Unload all active providers
390
+ this.activeProviders.forEach(name => {
391
+ const provider = ANALYTICS_PROVIDERS[name]
392
+ if (provider?.unload) {
393
+ provider.unload(this.config[name] || {})
394
+ }
395
+ })
396
+ this.activeProviders.clear()
397
+ }
398
+ }
399
+
400
+ export { ANALYTICS_PROVIDERS }