@el7ven/cookie-kit 0.3.1 → 0.3.3

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.
@@ -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'