@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,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TrackerDetector — automatically detects trackers on the page
|
|
3
|
+
* @module TrackerDetector
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getAllTrackerPatterns } from './TrackerPatterns.js'
|
|
7
|
+
|
|
8
|
+
export class TrackerDetector {
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
this.config = {
|
|
11
|
+
detectOnInit: true,
|
|
12
|
+
detectFromScripts: true,
|
|
13
|
+
detectFromGlobals: true,
|
|
14
|
+
detectFromDataLayer: true,
|
|
15
|
+
detectFromCookies: false, // Optional: can be privacy-invasive
|
|
16
|
+
detectFromNetwork: false, // Advanced: intercept fetch/XHR
|
|
17
|
+
observeDOM: true, // MutationObserver for dynamic trackers
|
|
18
|
+
confidenceScoring: true, // Calculate confidence scores
|
|
19
|
+
debug: false,
|
|
20
|
+
...config
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.detectedTrackers = new Map() // id -> tracker info
|
|
24
|
+
this.observer = null
|
|
25
|
+
this.networkInterceptors = []
|
|
26
|
+
this.detectTimer = null // Debounce timer
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Run full detection
|
|
31
|
+
* @returns {Array} Detected trackers
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Initialize detector with observers and interceptors
|
|
35
|
+
*/
|
|
36
|
+
initialize() {
|
|
37
|
+
// Start DOM observation
|
|
38
|
+
if (this.config.observeDOM) {
|
|
39
|
+
this._startDOMObserver()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Setup network interception
|
|
43
|
+
if (this.config.detectFromNetwork) {
|
|
44
|
+
this._setupNetworkInterception()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Initial detection
|
|
48
|
+
this.detect()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Run full detection
|
|
53
|
+
* @returns {Array} Detected trackers
|
|
54
|
+
*/
|
|
55
|
+
detect() {
|
|
56
|
+
this._debug('Starting tracker detection...')
|
|
57
|
+
|
|
58
|
+
const detectionResults = new Map() // id -> { sources: [], confidence: 0 }
|
|
59
|
+
|
|
60
|
+
// 1. Detect from script sources
|
|
61
|
+
if (this.config.detectFromScripts) {
|
|
62
|
+
const scriptTrackers = this._detectFromScripts()
|
|
63
|
+
scriptTrackers.forEach(({ id, confidence }) => {
|
|
64
|
+
this._addDetectionResult(detectionResults, id, 'script', confidence)
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 2. Detect from global variables
|
|
69
|
+
if (this.config.detectFromGlobals) {
|
|
70
|
+
const globalTrackers = this._detectFromGlobals()
|
|
71
|
+
globalTrackers.forEach(({ id, confidence }) => {
|
|
72
|
+
this._addDetectionResult(detectionResults, id, 'global', confidence)
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 3. Detect from dataLayer
|
|
77
|
+
if (this.config.detectFromDataLayer) {
|
|
78
|
+
const dataLayerTrackers = this._detectFromDataLayer()
|
|
79
|
+
dataLayerTrackers.forEach(({ id, confidence }) => {
|
|
80
|
+
this._addDetectionResult(detectionResults, id, 'dataLayer', confidence)
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 4. Detect from cookies (optional)
|
|
85
|
+
if (this.config.detectFromCookies) {
|
|
86
|
+
const cookieTrackers = this._detectFromCookies()
|
|
87
|
+
cookieTrackers.forEach(({ id, confidence }) => {
|
|
88
|
+
this._addDetectionResult(detectionResults, id, 'cookie', confidence)
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Store detected trackers with confidence scores
|
|
93
|
+
const patterns = getAllTrackerPatterns()
|
|
94
|
+
detectionResults.forEach((result, id) => {
|
|
95
|
+
const pattern = patterns.find(p => p.id === id)
|
|
96
|
+
if (pattern) {
|
|
97
|
+
const confidence = this.config.confidenceScoring
|
|
98
|
+
? this._calculateConfidence(result.sources)
|
|
99
|
+
: 1.0
|
|
100
|
+
|
|
101
|
+
// Update or add tracker (prevent duplicates, update confidence)
|
|
102
|
+
if (this.detectedTrackers.has(id)) {
|
|
103
|
+
const existing = this.detectedTrackers.get(id)
|
|
104
|
+
// Update confidence to maximum
|
|
105
|
+
existing.confidence = Math.max(existing.confidence, confidence)
|
|
106
|
+
// Merge detection sources
|
|
107
|
+
existing.detectedBy = [...new Set([...existing.detectedBy, ...result.sources])]
|
|
108
|
+
} else {
|
|
109
|
+
this.detectedTrackers.set(id, {
|
|
110
|
+
...pattern,
|
|
111
|
+
detectedAt: new Date().toISOString(),
|
|
112
|
+
detectedBy: result.sources,
|
|
113
|
+
confidence
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const trackers = Array.from(this.detectedTrackers.values())
|
|
120
|
+
this._debug(`Detected ${trackers.length} trackers:`, trackers.map(t => `${t.name} (${(t.confidence * 100).toFixed(0)}%)`))
|
|
121
|
+
|
|
122
|
+
return trackers
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Detect trackers from script sources
|
|
127
|
+
* @private
|
|
128
|
+
*/
|
|
129
|
+
_detectFromScripts() {
|
|
130
|
+
const detected = []
|
|
131
|
+
const scripts = document.querySelectorAll('script[src]')
|
|
132
|
+
const patterns = getAllTrackerPatterns()
|
|
133
|
+
|
|
134
|
+
scripts.forEach(script => {
|
|
135
|
+
const src = script.getAttribute('src') || ''
|
|
136
|
+
|
|
137
|
+
patterns.forEach(pattern => {
|
|
138
|
+
pattern.scripts.forEach(scriptPattern => {
|
|
139
|
+
if (src.includes(scriptPattern)) {
|
|
140
|
+
detected.push({ id: pattern.id, confidence: 0.9 })
|
|
141
|
+
this._debug(`Detected ${pattern.name} from script:`, src)
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
return detected
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Detect trackers from global variables
|
|
152
|
+
* @private
|
|
153
|
+
*/
|
|
154
|
+
_detectFromGlobals() {
|
|
155
|
+
const detected = []
|
|
156
|
+
const patterns = getAllTrackerPatterns()
|
|
157
|
+
|
|
158
|
+
patterns.forEach(pattern => {
|
|
159
|
+
pattern.globals.forEach(globalVar => {
|
|
160
|
+
if (typeof window !== 'undefined' && window[globalVar] !== undefined) {
|
|
161
|
+
detected.push({ id: pattern.id, confidence: 1.0 })
|
|
162
|
+
this._debug(`Detected ${pattern.name} from global:`, globalVar)
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
return detected
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Detect trackers from dataLayer
|
|
172
|
+
* @private
|
|
173
|
+
*/
|
|
174
|
+
_detectFromDataLayer() {
|
|
175
|
+
const detected = []
|
|
176
|
+
|
|
177
|
+
if (typeof window === 'undefined' || !window.dataLayer) {
|
|
178
|
+
return detected
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const patterns = getAllTrackerPatterns()
|
|
182
|
+
patterns.forEach(pattern => {
|
|
183
|
+
if (pattern.dataLayer.length === 0) return
|
|
184
|
+
|
|
185
|
+
pattern.dataLayer.forEach(key => {
|
|
186
|
+
const hasKey = window.dataLayer.some(item => {
|
|
187
|
+
if (typeof item === 'object') {
|
|
188
|
+
return key in item || JSON.stringify(item).includes(key)
|
|
189
|
+
}
|
|
190
|
+
return false
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
if (hasKey) {
|
|
194
|
+
detected.push({ id: pattern.id, confidence: 0.8 })
|
|
195
|
+
this._debug(`Detected ${pattern.name} from dataLayer:`, key)
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
return detected
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Detect trackers from cookies
|
|
205
|
+
* @private
|
|
206
|
+
*/
|
|
207
|
+
_detectFromCookies() {
|
|
208
|
+
const detected = []
|
|
209
|
+
|
|
210
|
+
if (typeof document === 'undefined') {
|
|
211
|
+
return detected
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const cookies = document.cookie
|
|
215
|
+
const patterns = getAllTrackerPatterns()
|
|
216
|
+
|
|
217
|
+
patterns.forEach(pattern => {
|
|
218
|
+
pattern.cookies.forEach(cookiePattern => {
|
|
219
|
+
if (cookies.includes(cookiePattern)) {
|
|
220
|
+
detected.push({ id: pattern.id, confidence: 0.7 })
|
|
221
|
+
this._debug(`Detected ${pattern.name} from cookie:`, cookiePattern)
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
return detected
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get detected trackers
|
|
231
|
+
* @returns {Array}
|
|
232
|
+
*/
|
|
233
|
+
getDetected() {
|
|
234
|
+
return Array.from(this.detectedTrackers.values())
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get detected trackers by category
|
|
239
|
+
* @param {string} category - Category name
|
|
240
|
+
* @returns {Array}
|
|
241
|
+
*/
|
|
242
|
+
getDetectedByCategory(category) {
|
|
243
|
+
return this.getDetected().filter(tracker => tracker.category === category)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Check if specific tracker is detected
|
|
248
|
+
* @param {string} id - Tracker ID
|
|
249
|
+
* @returns {boolean}
|
|
250
|
+
*/
|
|
251
|
+
isDetected(id) {
|
|
252
|
+
return this.detectedTrackers.has(id)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Get detection stats
|
|
257
|
+
* @returns {Object}
|
|
258
|
+
*/
|
|
259
|
+
getStats() {
|
|
260
|
+
const trackers = this.getDetected()
|
|
261
|
+
const byCategory = {}
|
|
262
|
+
|
|
263
|
+
trackers.forEach(tracker => {
|
|
264
|
+
if (!byCategory[tracker.category]) {
|
|
265
|
+
byCategory[tracker.category] = []
|
|
266
|
+
}
|
|
267
|
+
byCategory[tracker.category].push(tracker.name)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
total: trackers.length,
|
|
272
|
+
byCategory,
|
|
273
|
+
trackers: trackers.map(t => ({
|
|
274
|
+
id: t.id,
|
|
275
|
+
name: t.name,
|
|
276
|
+
category: t.category
|
|
277
|
+
}))
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Clear detected trackers
|
|
283
|
+
*/
|
|
284
|
+
clear() {
|
|
285
|
+
this.detectedTrackers.clear()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Re-run detection
|
|
290
|
+
* @returns {Array}
|
|
291
|
+
*/
|
|
292
|
+
refresh() {
|
|
293
|
+
this.clear()
|
|
294
|
+
return this.detect()
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Destroy detector and cleanup
|
|
299
|
+
*/
|
|
300
|
+
destroy() {
|
|
301
|
+
if (this.observer) {
|
|
302
|
+
this.observer.disconnect()
|
|
303
|
+
this.observer = null
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
this._removeNetworkInterception()
|
|
307
|
+
this.clear()
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// --- Advanced Detection Methods ---
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Start MutationObserver for dynamic trackers
|
|
314
|
+
* @private
|
|
315
|
+
*/
|
|
316
|
+
_startDOMObserver() {
|
|
317
|
+
if (typeof MutationObserver === 'undefined') {
|
|
318
|
+
return
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
this.observer = new MutationObserver((mutations) => {
|
|
322
|
+
let hasNewScripts = false
|
|
323
|
+
|
|
324
|
+
mutations.forEach((mutation) => {
|
|
325
|
+
mutation.addedNodes.forEach((node) => {
|
|
326
|
+
if (node.nodeType === 1) {
|
|
327
|
+
if (node.tagName === 'SCRIPT' || node.querySelector?.('script')) {
|
|
328
|
+
hasNewScripts = true
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
if (hasNewScripts) {
|
|
335
|
+
this._debug('New scripts detected, scheduling re-scan...')
|
|
336
|
+
this._scheduleDetect() // Debounced
|
|
337
|
+
}
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
this.observer.observe(document.documentElement, {
|
|
341
|
+
childList: true,
|
|
342
|
+
subtree: true
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
this._debug('DOM observer started')
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Setup network interception (fetch/XHR)
|
|
350
|
+
* SAFE: preserves original context and bindings
|
|
351
|
+
* @private
|
|
352
|
+
*/
|
|
353
|
+
_setupNetworkInterception() {
|
|
354
|
+
if (typeof window === 'undefined') return
|
|
355
|
+
|
|
356
|
+
const patterns = getAllTrackerPatterns()
|
|
357
|
+
|
|
358
|
+
// Intercept fetch (SAFE: bind original context)
|
|
359
|
+
if (window.fetch) {
|
|
360
|
+
const originalFetch = window.fetch.bind(window)
|
|
361
|
+
window.fetch = async (...args) => {
|
|
362
|
+
const url = args[0]?.toString() || ''
|
|
363
|
+
this._checkNetworkRequest(url, patterns)
|
|
364
|
+
return originalFetch(...args)
|
|
365
|
+
}
|
|
366
|
+
this.networkInterceptors.push({ type: 'fetch', original: originalFetch })
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Intercept XMLHttpRequest (SAFE: preserve prototype)
|
|
370
|
+
if (window.XMLHttpRequest) {
|
|
371
|
+
const originalOpen = XMLHttpRequest.prototype.open
|
|
372
|
+
const self = this
|
|
373
|
+
XMLHttpRequest.prototype.open = function(...args) {
|
|
374
|
+
const url = args[1]?.toString() || ''
|
|
375
|
+
self._checkNetworkRequest(url, patterns)
|
|
376
|
+
return originalOpen.apply(this, args)
|
|
377
|
+
}
|
|
378
|
+
this.networkInterceptors.push({ type: 'xhr', original: originalOpen })
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
this._debug('Network interception enabled (safe mode)')
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Check network request against patterns
|
|
386
|
+
* @private
|
|
387
|
+
*/
|
|
388
|
+
_checkNetworkRequest(url, patterns) {
|
|
389
|
+
patterns.forEach(pattern => {
|
|
390
|
+
pattern.network?.forEach(networkPattern => {
|
|
391
|
+
if (url.includes(networkPattern)) {
|
|
392
|
+
this._debug(`Detected ${pattern.name} from network:`, url)
|
|
393
|
+
|
|
394
|
+
// Update or add tracker
|
|
395
|
+
if (this.detectedTrackers.has(pattern.id)) {
|
|
396
|
+
const existing = this.detectedTrackers.get(pattern.id)
|
|
397
|
+
existing.confidence = Math.max(existing.confidence, 0.85)
|
|
398
|
+
if (!existing.detectedBy.includes('network')) {
|
|
399
|
+
existing.detectedBy.push('network')
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
this.detectedTrackers.set(pattern.id, {
|
|
403
|
+
...pattern,
|
|
404
|
+
detectedAt: new Date().toISOString(),
|
|
405
|
+
detectedBy: ['network'],
|
|
406
|
+
confidence: 0.85
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Remove network interception
|
|
416
|
+
* @private
|
|
417
|
+
*/
|
|
418
|
+
_removeNetworkInterception() {
|
|
419
|
+
this.networkInterceptors.forEach(({ type, original }) => {
|
|
420
|
+
if (type === 'fetch' && window.fetch) {
|
|
421
|
+
window.fetch = original
|
|
422
|
+
} else if (type === 'xhr' && XMLHttpRequest.prototype.open) {
|
|
423
|
+
XMLHttpRequest.prototype.open = original
|
|
424
|
+
}
|
|
425
|
+
})
|
|
426
|
+
this.networkInterceptors = []
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Add detection result
|
|
431
|
+
* @private
|
|
432
|
+
*/
|
|
433
|
+
_addDetectionResult(results, id, source, confidence) {
|
|
434
|
+
if (!results.has(id)) {
|
|
435
|
+
results.set(id, { sources: [], confidences: [] })
|
|
436
|
+
}
|
|
437
|
+
const result = results.get(id)
|
|
438
|
+
result.sources.push(source)
|
|
439
|
+
result.confidences.push(confidence)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Calculate overall confidence score
|
|
444
|
+
* @private
|
|
445
|
+
*/
|
|
446
|
+
_calculateConfidence(sources) {
|
|
447
|
+
if (sources.length === 0) return 0
|
|
448
|
+
|
|
449
|
+
// Weight different sources
|
|
450
|
+
const weights = {
|
|
451
|
+
script: 0.9,
|
|
452
|
+
global: 1.0,
|
|
453
|
+
dataLayer: 0.8,
|
|
454
|
+
cookie: 0.7,
|
|
455
|
+
network: 0.85
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
let totalWeight = 0
|
|
459
|
+
let weightedSum = 0
|
|
460
|
+
|
|
461
|
+
sources.forEach(source => {
|
|
462
|
+
const weight = weights[source] || 0.5
|
|
463
|
+
weightedSum += weight
|
|
464
|
+
totalWeight += 1
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
return totalWeight > 0 ? weightedSum / totalWeight : 0
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Schedule detection with debounce
|
|
472
|
+
* Prevents excessive detect() calls from MutationObserver
|
|
473
|
+
* @private
|
|
474
|
+
*/
|
|
475
|
+
_scheduleDetect() {
|
|
476
|
+
clearTimeout(this.detectTimer)
|
|
477
|
+
|
|
478
|
+
this.detectTimer = setTimeout(() => {
|
|
479
|
+
this.detect()
|
|
480
|
+
}, 200) // 200ms debounce
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
_debug(...args) {
|
|
484
|
+
if (this.config.debug) {
|
|
485
|
+
console.log('[TrackerDetector]', ...args)
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|