@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,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScriptManager — main controller for consent-based script activation
|
|
3
|
+
* @module ScriptManager
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ScriptScanner } from './ScriptScanner.js'
|
|
7
|
+
import { ScriptRegistry } from './ScriptRegistry.js'
|
|
8
|
+
import { ScriptLoader } from './ScriptLoader.js'
|
|
9
|
+
|
|
10
|
+
export class ScriptManager {
|
|
11
|
+
constructor(consentManager, config = {}) {
|
|
12
|
+
this.consentManager = consentManager
|
|
13
|
+
this.config = {
|
|
14
|
+
autoScan: true,
|
|
15
|
+
observeDOM: true,
|
|
16
|
+
removeBlockedScripts: true,
|
|
17
|
+
debug: false,
|
|
18
|
+
...config
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
this.scanner = new ScriptScanner({
|
|
22
|
+
observeDOM: this.config.observeDOM
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
this.registry = new ScriptRegistry()
|
|
26
|
+
this.loader = new ScriptLoader()
|
|
27
|
+
|
|
28
|
+
this.isInitialized = false
|
|
29
|
+
this._debug('ScriptManager initialized')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Initialize script management system
|
|
34
|
+
*/
|
|
35
|
+
async initialize() {
|
|
36
|
+
if (this.isInitialized) {
|
|
37
|
+
this._debug('Already initialized')
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Wait for DOM ready
|
|
42
|
+
if (document.readyState === 'loading') {
|
|
43
|
+
await new Promise(resolve => {
|
|
44
|
+
document.addEventListener('DOMContentLoaded', resolve, { once: true })
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Initial scan
|
|
49
|
+
if (this.config.autoScan) {
|
|
50
|
+
await this.scanAndRegister()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Apply current consent
|
|
54
|
+
const consent = this.consentManager.getConsent()
|
|
55
|
+
if (consent) {
|
|
56
|
+
await this.applyConsent(consent.categories)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Listen for consent changes
|
|
60
|
+
this.consentManager.on('consentChanged', async ({ categories }) => {
|
|
61
|
+
await this.applyConsent(categories)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Observe DOM for new scripts
|
|
65
|
+
if (this.config.observeDOM) {
|
|
66
|
+
this.scanner.observe((newScripts) => {
|
|
67
|
+
this._debug('New scripts detected:', newScripts.length)
|
|
68
|
+
this.registry.addMany(newScripts)
|
|
69
|
+
|
|
70
|
+
// Auto-activate if consent already given
|
|
71
|
+
const consent = this.consentManager.getConsent()
|
|
72
|
+
if (consent) {
|
|
73
|
+
this.activateScriptsByConsent(newScripts, consent.categories)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.isInitialized = true
|
|
79
|
+
this._debug('ScriptManager ready')
|
|
80
|
+
this._emitEvent('scripts:ready', { stats: this.registry.getStats() })
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Scan DOM and register all scripts
|
|
85
|
+
*/
|
|
86
|
+
async scanAndRegister() {
|
|
87
|
+
this._debug('Scanning DOM for scripts...')
|
|
88
|
+
const scripts = this.scanner.scan()
|
|
89
|
+
this._debug(`Found ${scripts.length} scripts`)
|
|
90
|
+
|
|
91
|
+
this.registry.addMany(scripts)
|
|
92
|
+
|
|
93
|
+
this._emitEvent('scripts:scanned', {
|
|
94
|
+
total: scripts.length,
|
|
95
|
+
stats: this.registry.getStats()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
return scripts
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Apply consent to scripts
|
|
103
|
+
* @param {Object} categories - Consent categories
|
|
104
|
+
*/
|
|
105
|
+
async applyConsent(categories) {
|
|
106
|
+
this._debug('Applying consent:', categories)
|
|
107
|
+
|
|
108
|
+
const results = {
|
|
109
|
+
activated: [],
|
|
110
|
+
blocked: [],
|
|
111
|
+
failed: []
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Get all blocked scripts
|
|
115
|
+
const blockedScripts = this.registry.getBlocked()
|
|
116
|
+
|
|
117
|
+
for (const script of blockedScripts) {
|
|
118
|
+
const hasConsent = categories[script.category] === true
|
|
119
|
+
|
|
120
|
+
if (hasConsent) {
|
|
121
|
+
// Activate script
|
|
122
|
+
const success = await this.activateScript(script)
|
|
123
|
+
|
|
124
|
+
if (success) {
|
|
125
|
+
results.activated.push(script)
|
|
126
|
+
} else {
|
|
127
|
+
results.failed.push(script)
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
results.blocked.push(script)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this._debug('Consent applied:', {
|
|
135
|
+
activated: results.activated.length,
|
|
136
|
+
blocked: results.blocked.length,
|
|
137
|
+
failed: results.failed.length
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
this._emitEvent('scripts:consent-applied', results)
|
|
141
|
+
|
|
142
|
+
return results
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Activate single script
|
|
147
|
+
* @param {Object} script - Script descriptor
|
|
148
|
+
* @returns {Promise<boolean>}
|
|
149
|
+
*/
|
|
150
|
+
async activateScript(script) {
|
|
151
|
+
if (script.isActivated) {
|
|
152
|
+
this._debug('Script already activated:', script.id)
|
|
153
|
+
return true
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this._debug('Activating script:', script.id, script.category)
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
// Load script
|
|
160
|
+
const success = await this.loader.load(script)
|
|
161
|
+
|
|
162
|
+
if (success) {
|
|
163
|
+
// Mark as activated
|
|
164
|
+
this.registry.markActivated(script.id)
|
|
165
|
+
|
|
166
|
+
// Remove original blocked element
|
|
167
|
+
if (this.config.removeBlockedScripts && script.element) {
|
|
168
|
+
this.loader.removeBlockedScript(script.element)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this._emitEvent('script:activated', {
|
|
172
|
+
id: script.id,
|
|
173
|
+
category: script.category,
|
|
174
|
+
src: script.src || 'inline'
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
return true
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return false
|
|
181
|
+
} catch (error) {
|
|
182
|
+
return false
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Activate scripts by consent (helper for new scripts)
|
|
188
|
+
* @param {Array} scripts - Scripts to check
|
|
189
|
+
* @param {Object} categories - Consent categories
|
|
190
|
+
*/
|
|
191
|
+
async activateScriptsByConsent(scripts, categories) {
|
|
192
|
+
const results = []
|
|
193
|
+
|
|
194
|
+
for (const script of scripts) {
|
|
195
|
+
if (script.isBlocked && !script.isActivated) {
|
|
196
|
+
const hasConsent = categories[script.category] === true
|
|
197
|
+
|
|
198
|
+
if (hasConsent) {
|
|
199
|
+
const success = await this.activateScript(script)
|
|
200
|
+
results.push({ script, success })
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return results
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get scripts by category
|
|
210
|
+
* @param {string} category - Category name
|
|
211
|
+
* @returns {Array}
|
|
212
|
+
*/
|
|
213
|
+
getScriptsByCategory(category) {
|
|
214
|
+
return this.registry.getByCategory(category)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get all registered scripts
|
|
219
|
+
* @returns {Array}
|
|
220
|
+
*/
|
|
221
|
+
getAllScripts() {
|
|
222
|
+
return this.registry.getAll()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get registry stats
|
|
227
|
+
* @returns {Object}
|
|
228
|
+
*/
|
|
229
|
+
getStats() {
|
|
230
|
+
return this.registry.getStats()
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get blocked scripts count
|
|
235
|
+
* @returns {number}
|
|
236
|
+
*/
|
|
237
|
+
getBlockedCount() {
|
|
238
|
+
return this.registry.getBlocked().length
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Get activated scripts count
|
|
243
|
+
* @returns {number}
|
|
244
|
+
*/
|
|
245
|
+
getActivatedCount() {
|
|
246
|
+
return this.registry.getActivated().length
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Manually trigger rescan
|
|
251
|
+
*/
|
|
252
|
+
async rescan() {
|
|
253
|
+
return this.scanAndRegister()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Destroy script manager
|
|
258
|
+
*/
|
|
259
|
+
destroy() {
|
|
260
|
+
this.scanner.disconnect()
|
|
261
|
+
this.registry.clear()
|
|
262
|
+
this.loader.clear()
|
|
263
|
+
this.isInitialized = false
|
|
264
|
+
this._debug('ScriptManager destroyed')
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// --- Internal helpers ---
|
|
268
|
+
|
|
269
|
+
_emitEvent(eventName, detail) {
|
|
270
|
+
if (typeof window !== 'undefined') {
|
|
271
|
+
window.dispatchEvent(new CustomEvent(`cookie-consent:${eventName}`, { detail }))
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
_debug(...args) {
|
|
276
|
+
// Debug disabled
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScriptRegistry — stores and manages discovered scripts
|
|
3
|
+
* @module ScriptRegistry
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class ScriptRegistry {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.scripts = new Map() // id -> script descriptor
|
|
9
|
+
this.categorizedScripts = new Map() // category -> [scripts]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Add script to registry
|
|
14
|
+
* @param {Object} script - Script descriptor
|
|
15
|
+
*/
|
|
16
|
+
add(script) {
|
|
17
|
+
if (!script.id) return
|
|
18
|
+
|
|
19
|
+
// Store by id
|
|
20
|
+
this.scripts.set(script.id, script)
|
|
21
|
+
|
|
22
|
+
// Store by category
|
|
23
|
+
if (script.category) {
|
|
24
|
+
if (!this.categorizedScripts.has(script.category)) {
|
|
25
|
+
this.categorizedScripts.set(script.category, [])
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const categoryScripts = this.categorizedScripts.get(script.category)
|
|
29
|
+
if (!categoryScripts.find(s => s.id === script.id)) {
|
|
30
|
+
categoryScripts.push(script)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Add multiple scripts
|
|
37
|
+
* @param {Array} scripts - Array of script descriptors
|
|
38
|
+
*/
|
|
39
|
+
addMany(scripts) {
|
|
40
|
+
scripts.forEach(script => this.add(script))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get script by id
|
|
45
|
+
* @param {string} id - Script id
|
|
46
|
+
* @returns {Object|null}
|
|
47
|
+
*/
|
|
48
|
+
get(id) {
|
|
49
|
+
return this.scripts.get(id) || null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get all scripts
|
|
54
|
+
* @returns {Array}
|
|
55
|
+
*/
|
|
56
|
+
getAll() {
|
|
57
|
+
return Array.from(this.scripts.values())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get scripts by category
|
|
62
|
+
* @param {string} category - Category name
|
|
63
|
+
* @returns {Array}
|
|
64
|
+
*/
|
|
65
|
+
getByCategory(category) {
|
|
66
|
+
return this.categorizedScripts.get(category) || []
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get all categories
|
|
71
|
+
* @returns {Array}
|
|
72
|
+
*/
|
|
73
|
+
getCategories() {
|
|
74
|
+
return Array.from(this.categorizedScripts.keys())
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get blocked scripts (not yet activated)
|
|
79
|
+
* @returns {Array}
|
|
80
|
+
*/
|
|
81
|
+
getBlocked() {
|
|
82
|
+
return this.getAll().filter(script => script.isBlocked && !script.isActivated)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get activated scripts
|
|
87
|
+
* @returns {Array}
|
|
88
|
+
*/
|
|
89
|
+
getActivated() {
|
|
90
|
+
return this.getAll().filter(script => script.isActivated)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Mark script as activated
|
|
95
|
+
* @param {string} id - Script id
|
|
96
|
+
*/
|
|
97
|
+
markActivated(id) {
|
|
98
|
+
const script = this.scripts.get(id)
|
|
99
|
+
if (script) {
|
|
100
|
+
script.isActivated = true
|
|
101
|
+
script.activatedAt = new Date().toISOString()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Remove script from registry
|
|
107
|
+
* @param {string} id - Script id
|
|
108
|
+
*/
|
|
109
|
+
remove(id) {
|
|
110
|
+
const script = this.scripts.get(id)
|
|
111
|
+
if (!script) return
|
|
112
|
+
|
|
113
|
+
// Remove from main map
|
|
114
|
+
this.scripts.delete(id)
|
|
115
|
+
|
|
116
|
+
// Remove from category map
|
|
117
|
+
if (script.category) {
|
|
118
|
+
const categoryScripts = this.categorizedScripts.get(script.category)
|
|
119
|
+
if (categoryScripts) {
|
|
120
|
+
const index = categoryScripts.findIndex(s => s.id === id)
|
|
121
|
+
if (index !== -1) {
|
|
122
|
+
categoryScripts.splice(index, 1)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Clear all scripts
|
|
130
|
+
*/
|
|
131
|
+
clear() {
|
|
132
|
+
this.scripts.clear()
|
|
133
|
+
this.categorizedScripts.clear()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get registry stats
|
|
138
|
+
* @returns {Object}
|
|
139
|
+
*/
|
|
140
|
+
getStats() {
|
|
141
|
+
const all = this.getAll()
|
|
142
|
+
return {
|
|
143
|
+
total: all.length,
|
|
144
|
+
blocked: all.filter(s => s.isBlocked && !s.isActivated).length,
|
|
145
|
+
activated: all.filter(s => s.isActivated).length,
|
|
146
|
+
byCategory: Object.fromEntries(
|
|
147
|
+
Array.from(this.categorizedScripts.entries()).map(([cat, scripts]) => [
|
|
148
|
+
cat,
|
|
149
|
+
{
|
|
150
|
+
total: scripts.length,
|
|
151
|
+
blocked: scripts.filter(s => s.isBlocked && !s.isActivated).length,
|
|
152
|
+
activated: scripts.filter(s => s.isActivated).length
|
|
153
|
+
}
|
|
154
|
+
])
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Check if script exists
|
|
161
|
+
* @param {string} id - Script id
|
|
162
|
+
* @returns {boolean}
|
|
163
|
+
*/
|
|
164
|
+
has(id) {
|
|
165
|
+
return this.scripts.has(id)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get count of scripts
|
|
170
|
+
* @returns {number}
|
|
171
|
+
*/
|
|
172
|
+
count() {
|
|
173
|
+
return this.scripts.size
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScriptScanner — scans DOM for blocked scripts with data-cookie-category
|
|
3
|
+
* @module ScriptScanner
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class ScriptScanner {
|
|
7
|
+
constructor(config = {}) {
|
|
8
|
+
this.config = {
|
|
9
|
+
attributeName: 'data-cookie-category',
|
|
10
|
+
srcAttributeName: 'data-cookie-src',
|
|
11
|
+
blockedTypes: ['text/plain', 'text/blocked'],
|
|
12
|
+
scanOnInit: true,
|
|
13
|
+
observeDOM: true,
|
|
14
|
+
scanIframes: true,
|
|
15
|
+
...config
|
|
16
|
+
}
|
|
17
|
+
this.observer = null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Scan DOM for blocked scripts
|
|
22
|
+
* @returns {Array} Array of script descriptors
|
|
23
|
+
*/
|
|
24
|
+
scan() {
|
|
25
|
+
const items = []
|
|
26
|
+
|
|
27
|
+
// Find all script tags with data-cookie-category
|
|
28
|
+
const scriptElements = document.querySelectorAll(`script[${this.config.attributeName}]`)
|
|
29
|
+
|
|
30
|
+
scriptElements.forEach((element, index) => {
|
|
31
|
+
const category = element.getAttribute(this.config.attributeName)
|
|
32
|
+
const type = element.getAttribute('type')
|
|
33
|
+
const isBlocked = this.config.blockedTypes.includes(type)
|
|
34
|
+
|
|
35
|
+
// Pre-blocking: use data-cookie-src instead of src
|
|
36
|
+
const cookieSrc = element.getAttribute(this.config.srcAttributeName)
|
|
37
|
+
const regularSrc = element.getAttribute('src')
|
|
38
|
+
const actualSrc = cookieSrc || regularSrc
|
|
39
|
+
|
|
40
|
+
const descriptor = {
|
|
41
|
+
id: `script-${index}-${Date.now()}`,
|
|
42
|
+
elementType: 'script',
|
|
43
|
+
element,
|
|
44
|
+
category,
|
|
45
|
+
type: type || 'text/javascript',
|
|
46
|
+
src: actualSrc || null,
|
|
47
|
+
cookieSrc: cookieSrc || null,
|
|
48
|
+
inline: !actualSrc,
|
|
49
|
+
content: !actualSrc ? element.textContent : null,
|
|
50
|
+
isBlocked,
|
|
51
|
+
isActivated: false,
|
|
52
|
+
attributes: this._getAttributes(element)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
items.push(descriptor)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Scan iframes if enabled
|
|
59
|
+
if (this.config.scanIframes) {
|
|
60
|
+
const iframeElements = document.querySelectorAll(`iframe[${this.config.attributeName}]`)
|
|
61
|
+
|
|
62
|
+
iframeElements.forEach((element, index) => {
|
|
63
|
+
const category = element.getAttribute(this.config.attributeName)
|
|
64
|
+
const cookieSrc = element.getAttribute(this.config.srcAttributeName)
|
|
65
|
+
const regularSrc = element.getAttribute('src')
|
|
66
|
+
const actualSrc = cookieSrc || regularSrc
|
|
67
|
+
|
|
68
|
+
const descriptor = {
|
|
69
|
+
id: `iframe-${index}-${Date.now()}`,
|
|
70
|
+
elementType: 'iframe',
|
|
71
|
+
element,
|
|
72
|
+
category,
|
|
73
|
+
src: actualSrc || null,
|
|
74
|
+
cookieSrc: cookieSrc || null,
|
|
75
|
+
isBlocked: true, // iframes always blocked until consent
|
|
76
|
+
isActivated: false,
|
|
77
|
+
attributes: this._getAttributes(element)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
items.push(descriptor)
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return items
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Start observing DOM for new scripts
|
|
89
|
+
* @param {Function} callback - Called when new scripts detected
|
|
90
|
+
*/
|
|
91
|
+
observe(callback) {
|
|
92
|
+
if (!this.config.observeDOM || typeof MutationObserver === 'undefined') {
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.observer = new MutationObserver((mutations) => {
|
|
97
|
+
let hasNewScripts = false
|
|
98
|
+
|
|
99
|
+
mutations.forEach((mutation) => {
|
|
100
|
+
mutation.addedNodes.forEach((node) => {
|
|
101
|
+
if (node.nodeType === 1) { // Element node
|
|
102
|
+
// Check if added node is a script
|
|
103
|
+
if (node.tagName === 'SCRIPT' && node.hasAttribute(this.config.attributeName)) {
|
|
104
|
+
hasNewScripts = true
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check if added node contains scripts
|
|
108
|
+
const scripts = node.querySelectorAll?.(`script[${this.config.attributeName}]`)
|
|
109
|
+
if (scripts?.length > 0) {
|
|
110
|
+
hasNewScripts = true
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
if (hasNewScripts) {
|
|
117
|
+
const newScripts = this.scan()
|
|
118
|
+
callback(newScripts)
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
this.observer.observe(document.documentElement, {
|
|
123
|
+
childList: true,
|
|
124
|
+
subtree: true
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Stop observing DOM
|
|
130
|
+
*/
|
|
131
|
+
disconnect() {
|
|
132
|
+
if (this.observer) {
|
|
133
|
+
this.observer.disconnect()
|
|
134
|
+
this.observer = null
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get all attributes from element (except data-cookie-category)
|
|
140
|
+
* @private
|
|
141
|
+
*/
|
|
142
|
+
_getAttributes(element) {
|
|
143
|
+
const attributes = {}
|
|
144
|
+
const excludeAttrs = [
|
|
145
|
+
this.config.attributeName,
|
|
146
|
+
this.config.srcAttributeName,
|
|
147
|
+
'type',
|
|
148
|
+
'src' // Exclude src to prevent early execution
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
Array.from(element.attributes).forEach((attr) => {
|
|
152
|
+
if (!excludeAttrs.includes(attr.name)) {
|
|
153
|
+
attributes[attr.name] = attr.value
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
return attributes
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Find scripts by category
|
|
162
|
+
* @param {string} category - Category name
|
|
163
|
+
* @returns {Array} Filtered scripts
|
|
164
|
+
*/
|
|
165
|
+
findByCategory(category) {
|
|
166
|
+
return this.scan().filter(script => script.category === category)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Check if script should be blocked
|
|
171
|
+
* @param {HTMLScriptElement} element - Script element
|
|
172
|
+
* @returns {boolean}
|
|
173
|
+
*/
|
|
174
|
+
isBlocked(element) {
|
|
175
|
+
const type = element.getAttribute('type')
|
|
176
|
+
return this.config.blockedTypes.includes(type)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script Management System — Auto-blocking scripts (Cookiebot-style)
|
|
3
|
+
* @module scripts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { ScriptScanner } from './ScriptScanner.js'
|
|
7
|
+
export { ScriptRegistry } from './ScriptRegistry.js'
|
|
8
|
+
export { ScriptLoader } from './ScriptLoader.js'
|
|
9
|
+
export { ScriptManager } from './ScriptManager.js'
|