@el7ven/cookie-kit 0.2.21 → 0.3.1

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.2.21",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "module": "./src/index.js",
@@ -127,27 +127,115 @@ export function loadYandexMetrica(counterId) {
127
127
  }
128
128
 
129
129
  /**
130
- * Block/unblock scripts based on consent categories
131
- * Scripts with data-cookie-category attribute will be managed
132
- * @param {Object} categories - { analytics: true, marketing: false }
130
+ * Block all scripts with data-cookie-category BEFORE consent.
131
+ * Call this as early as possible (before DOMContentLoaded).
132
+ * Rewrites script type to text/plain so they don't execute.
133
+ *
134
+ * Usage in HTML:
135
+ * <script data-cookie-category="analytics" src="..."></script>
136
+ * <script data-cookie-category="marketing">inline code</script>
133
137
  */
134
- export function manageScriptBlocking(categories = {}) {
138
+ export function blockScriptsBeforeConsent() {
135
139
  if (typeof document === 'undefined') return
136
140
 
141
+ // MutationObserver to catch dynamically added scripts
142
+ const observer = new MutationObserver((mutations) => {
143
+ for (const mutation of mutations) {
144
+ for (const node of mutation.addedNodes) {
145
+ if (node.tagName === 'SCRIPT' && node.getAttribute('data-cookie-category')) {
146
+ const category = node.getAttribute('data-cookie-category')
147
+ // Check if consent already given for this category
148
+ if (!_hasConsentFor(category)) {
149
+ node.type = 'text/plain'
150
+ if (node.src) {
151
+ node.dataset.originalSrc = node.src
152
+ node.removeAttribute('src')
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ })
159
+
160
+ observer.observe(document.documentElement, { childList: true, subtree: true })
161
+
162
+ // Also block any scripts already in the DOM
137
163
  document.querySelectorAll('script[data-cookie-category]').forEach(script => {
164
+ const category = script.getAttribute('data-cookie-category')
165
+ if (!_hasConsentFor(category)) {
166
+ if (!script.dataset.blocked) {
167
+ script.dataset.blocked = 'true'
168
+ // Already executed scripts can't be un-executed,
169
+ // but we mark them so future loads respect consent
170
+ }
171
+ }
172
+ })
173
+
174
+ // Store observer reference for cleanup
175
+ if (typeof window !== 'undefined') {
176
+ window.__cookieKitScriptObserver = observer
177
+ }
178
+
179
+ return observer
180
+ }
181
+
182
+ /**
183
+ * Unblock and execute scripts for consented categories.
184
+ * @param {Object} categories - { analytics: true, marketing: false }
185
+ */
186
+ export function unblockConsentedScripts(categories = {}) {
187
+ if (typeof document === 'undefined') return
188
+
189
+ document.querySelectorAll('script[data-cookie-category][type="text/plain"]').forEach(script => {
138
190
  const category = script.getAttribute('data-cookie-category')
139
191
  if (categories[category]) {
140
- // Re-enable script
141
- if (script.type === 'text/plain' && script.dataset.originalSrc) {
142
- const newScript = document.createElement('script')
192
+ const newScript = document.createElement('script')
193
+
194
+ // Copy attributes
195
+ for (const attr of script.attributes) {
196
+ if (attr.name !== 'type' && attr.name !== 'data-original-src') {
197
+ newScript.setAttribute(attr.name, attr.value)
198
+ }
199
+ }
200
+
201
+ // Restore src or inline content
202
+ if (script.dataset.originalSrc) {
143
203
  newScript.src = script.dataset.originalSrc
144
204
  newScript.async = true
145
- script.parentNode.replaceChild(newScript, script)
205
+ } else if (script.textContent) {
206
+ newScript.textContent = script.textContent
146
207
  }
208
+
209
+ newScript.type = 'text/javascript'
210
+ script.parentNode.replaceChild(newScript, script)
147
211
  }
148
212
  })
149
213
  }
150
214
 
215
+ /**
216
+ * Check localStorage for existing consent for a category
217
+ * @private
218
+ */
219
+ function _hasConsentFor(category) {
220
+ if (typeof localStorage === 'undefined') return false
221
+ try {
222
+ const stored = localStorage.getItem('cookie_consent')
223
+ if (!stored) return false
224
+ const consent = JSON.parse(stored)
225
+ return consent?.categories?.[category] === true
226
+ } catch {
227
+ return false
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Legacy alias
233
+ * @param {Object} categories - { analytics: true, marketing: false }
234
+ */
235
+ export function manageScriptBlocking(categories = {}) {
236
+ unblockConsentedScripts(categories)
237
+ }
238
+
151
239
  /**
152
240
  * Create analytics manager that auto-integrates with cookie consent
153
241
  * @param {Object} config - { ga4: 'G-XXX', gtm: 'GTM-XXX', metaPixel: 'XXX', yandexMetrica: 'XXX' }
@@ -160,6 +248,9 @@ export function createAnalyticsManager(config = {}) {
160
248
  if (initialized) return
161
249
  initialized = true
162
250
 
251
+ // Block scripts before consent
252
+ blockScriptsBeforeConsent()
253
+
163
254
  // Always init Google Consent Mode first (denied by default)
164
255
  initGoogleConsentMode()
165
256
 
@@ -181,8 +272,8 @@ export function createAnalyticsManager(config = {}) {
181
272
  if (config.metaPixel) loadMetaPixel(config.metaPixel)
182
273
  }
183
274
 
184
- // Manage script blocking
185
- manageScriptBlocking(categories)
275
+ // Unblock scripts for consented categories
276
+ unblockConsentedScripts(categories)
186
277
  }
187
278
 
188
279
  const connectToCookieConsent = () => {
package/src/core/index.js CHANGED
@@ -32,7 +32,7 @@ const DEFAULT_CONFIG = {
32
32
  label: 'Necesare',
33
33
  description: 'Cookie-uri esențiale pentru funcționarea site-ului',
34
34
  required: true,
35
- locked: true,
35
+ disabled: true,
36
36
  enabled: true
37
37
  },
38
38
  analytics: {
@@ -40,24 +40,24 @@ const DEFAULT_CONFIG = {
40
40
  label: 'Analiză',
41
41
  description: 'Cookie-uri pentru analiza traficului și comportamentului utilizatorilor',
42
42
  required: false,
43
- locked: false,
44
- enabled: true
43
+ disabled: false,
44
+ enabled: false
45
45
  },
46
46
  marketing: {
47
47
  id: 'marketing',
48
48
  label: 'Marketing',
49
49
  description: 'Cookie-uri pentru publicitate personalizată',
50
50
  required: false,
51
- locked: false,
52
- enabled: true
51
+ disabled: false,
52
+ enabled: false
53
53
  },
54
54
  preferences: {
55
55
  id: 'preferences',
56
56
  label: 'Preferințe',
57
57
  description: 'Cookie-uri pentru salvarea preferințelor utilizatorului',
58
58
  required: false,
59
- locked: false,
60
- enabled: true
59
+ disabled: false,
60
+ enabled: false
61
61
  }
62
62
  },
63
63
 
package/src/index.js CHANGED
@@ -9,7 +9,7 @@
9
9
  export { createCookieKit, CookieKitCore, DEFAULT_CONFIG } from './core/index.js'
10
10
 
11
11
  // Analytics integration
12
- export { createAnalyticsManager, initGoogleConsentMode, updateGoogleConsent, loadGTM, loadGA4, loadMetaPixel, loadYandexMetrica, manageScriptBlocking } from './core/analytics.js'
12
+ export { createAnalyticsManager, initGoogleConsentMode, updateGoogleConsent, loadGTM, loadGA4, loadMetaPixel, loadYandexMetrica, blockScriptsBeforeConsent, unblockConsentedScripts, manageScriptBlocking } from './core/analytics.js'
13
13
 
14
14
  // Import for default export
15
15
  import { createCookieKit, CookieKitCore, DEFAULT_CONFIG } from './core/index.js'
@@ -386,7 +386,6 @@ class CookieConsent extends HTMLElement {
386
386
  /* Header */
387
387
  .cookie-drawer__header {
388
388
  padding: 24px 24px 16px;
389
- border-bottom: 1px solid var(--ck-border);
390
389
  display: flex;
391
390
  justify-content: space-between;
392
391
  align-items: flex-start;
@@ -206,7 +206,7 @@ const handleSelectTab = (tabId) => {
206
206
 
207
207
  const handleToggleCategory = (categoryId) => {
208
208
  const cat = mergedConfig.value.categories[categoryId]
209
- if (cat && !cat.locked && !cat.required) {
209
+ if (cat && !cat.disabled && !cat.required) {
210
210
  categories.value[categoryId] = !categories.value[categoryId]
211
211
  }
212
212
  }
@@ -71,14 +71,14 @@
71
71
  :id="`drawer-category-${category.id}`"
72
72
  :checked="categories[category.id]"
73
73
  @change="toggleCategory(category.id)"
74
- :disabled="category.locked"
74
+ :disabled="category.disabled"
75
75
  class="cookie-drawer__toggle-input"
76
76
  />
77
77
  <label
78
78
  :for="`drawer-category-${category.id}`"
79
79
  :class="[
80
80
  'cookie-drawer__toggle-label',
81
- { 'cookie-drawer__toggle-label--disabled': category.locked }
81
+ { 'cookie-drawer__toggle-label--disabled': category.disabled }
82
82
  ]"
83
83
  ></label>
84
84
  </div>
@@ -154,14 +154,14 @@
154
154
  :id="`settings-category-${currentTab}`"
155
155
  :checked="categories[currentTab]"
156
156
  @change="toggleCategory(currentTab)"
157
- :disabled="currentCategoryData.locked"
157
+ :disabled="currentCategoryData.disabled"
158
158
  class="cookie-drawer__toggle-input"
159
159
  />
160
160
  <label
161
161
  :for="`settings-category-${currentTab}`"
162
162
  :class="[
163
163
  'cookie-drawer__toggle-label',
164
- { 'cookie-drawer__toggle-label--disabled': currentCategoryData.locked }
164
+ { 'cookie-drawer__toggle-label--disabled': currentCategoryData.disabled }
165
165
  ]"
166
166
  ></label>
167
167
  </div>