@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,307 @@
1
+ /**
2
+ * Tracker Patterns — extensible detection patterns for popular trackers
3
+ * @module TrackerPatterns
4
+ */
5
+
6
+ class TrackerPatternRegistry {
7
+ constructor() {
8
+ this.patterns = new Map()
9
+ this._registerDefaultPatterns()
10
+ }
11
+
12
+ /**
13
+ * Register default tracker patterns
14
+ * @private
15
+ */
16
+ _registerDefaultPatterns() {
17
+ const defaultPatterns = [
18
+ // Google Analytics
19
+ {
20
+ id: 'google-analytics',
21
+ name: 'Google Analytics',
22
+ category: 'analytics',
23
+ scripts: [
24
+ 'google-analytics.com/analytics.js',
25
+ 'google-analytics.com/ga.js',
26
+ 'googletagmanager.com/gtag/js'
27
+ ],
28
+ globals: ['ga', 'gtag', '_gaq'],
29
+ cookies: ['_ga', '_gid', '_gat'],
30
+ dataLayer: ['gtag'],
31
+ network: ['google-analytics.com', 'analytics.google.com']
32
+ },
33
+
34
+ // Google Tag Manager
35
+ {
36
+ id: 'google-tag-manager',
37
+ name: 'Google Tag Manager',
38
+ category: 'analytics',
39
+ scripts: ['googletagmanager.com/gtm.js'],
40
+ globals: ['google_tag_manager', 'dataLayer'],
41
+ cookies: ['_ga', '_gid'],
42
+ dataLayer: ['gtm.start', 'gtm.js'],
43
+ network: ['googletagmanager.com']
44
+ },
45
+
46
+ // Facebook Pixel
47
+ {
48
+ id: 'facebook-pixel',
49
+ name: 'Facebook Pixel',
50
+ category: 'marketing',
51
+ scripts: ['connect.facebook.net/en_US/fbevents.js', 'connect.facebook.net'],
52
+ globals: ['fbq', '_fbq'],
53
+ cookies: ['_fbp', 'fr'],
54
+ dataLayer: [],
55
+ network: ['connect.facebook.net', 'facebook.com/tr']
56
+ },
57
+
58
+ // Hotjar
59
+ {
60
+ id: 'hotjar',
61
+ name: 'Hotjar',
62
+ category: 'analytics',
63
+ scripts: ['static.hotjar.com/c/hotjar-', 'script.hotjar.com'],
64
+ globals: ['hj', '_hjSettings'],
65
+ cookies: ['_hjid', '_hjIncludedInSample'],
66
+ dataLayer: [],
67
+ network: ['static.hotjar.com', 'script.hotjar.com']
68
+ },
69
+
70
+ // Microsoft Clarity
71
+ {
72
+ id: 'microsoft-clarity',
73
+ name: 'Microsoft Clarity',
74
+ category: 'analytics',
75
+ scripts: ['clarity.ms/tag/', 'www.clarity.ms'],
76
+ globals: ['clarity'],
77
+ cookies: ['_clck', '_clsk'],
78
+ dataLayer: [],
79
+ network: ['clarity.ms']
80
+ },
81
+
82
+ // TikTok Pixel
83
+ {
84
+ id: 'tiktok-pixel',
85
+ name: 'TikTok Pixel',
86
+ category: 'marketing',
87
+ scripts: ['analytics.tiktok.com/i18n/pixel/events.js'],
88
+ globals: ['ttq'],
89
+ cookies: ['_ttp'],
90
+ dataLayer: [],
91
+ network: ['analytics.tiktok.com']
92
+ },
93
+
94
+ // LinkedIn Insight
95
+ {
96
+ id: 'linkedin-insight',
97
+ name: 'LinkedIn Insight Tag',
98
+ category: 'marketing',
99
+ scripts: ['snap.licdn.com/li.lms-analytics/insight.min.js'],
100
+ globals: ['_linkedin_data_partner_ids'],
101
+ cookies: ['li_sugr', 'lidc'],
102
+ dataLayer: [],
103
+ network: ['snap.licdn.com']
104
+ },
105
+
106
+ // Google Ads
107
+ {
108
+ id: 'google-ads',
109
+ name: 'Google Ads',
110
+ category: 'marketing',
111
+ scripts: ['googleadservices.com/pagead/conversion', 'www.googleadservices.com'],
112
+ globals: ['google_trackConversion'],
113
+ cookies: ['_gcl_au', '_gcl_aw'],
114
+ dataLayer: [],
115
+ network: ['googleadservices.com']
116
+ },
117
+
118
+ // Yandex Metrica
119
+ {
120
+ id: 'yandex-metrica',
121
+ name: 'Yandex Metrica',
122
+ category: 'analytics',
123
+ scripts: ['mc.yandex.ru/metrika/watch.js', 'mc.yandex.ru/metrika/tag.js'],
124
+ globals: ['ym', 'Ya'],
125
+ cookies: ['_ym_uid', '_ym_d'],
126
+ dataLayer: [],
127
+ network: ['mc.yandex.ru']
128
+ },
129
+
130
+ // Mixpanel
131
+ {
132
+ id: 'mixpanel',
133
+ name: 'Mixpanel',
134
+ category: 'analytics',
135
+ scripts: ['cdn.mxpnl.com/libs/mixpanel'],
136
+ globals: ['mixpanel'],
137
+ cookies: ['mp_'],
138
+ dataLayer: [],
139
+ network: ['cdn.mxpnl.com', 'api.mixpanel.com']
140
+ },
141
+
142
+ // Segment
143
+ {
144
+ id: 'segment',
145
+ name: 'Segment',
146
+ category: 'analytics',
147
+ scripts: ['cdn.segment.com/analytics.js'],
148
+ globals: ['analytics'],
149
+ cookies: ['ajs_'],
150
+ dataLayer: [],
151
+ network: ['cdn.segment.com', 'api.segment.io']
152
+ },
153
+
154
+ // Intercom
155
+ {
156
+ id: 'intercom',
157
+ name: 'Intercom',
158
+ category: 'preferences',
159
+ scripts: ['widget.intercom.io'],
160
+ globals: ['Intercom'],
161
+ cookies: ['intercom-'],
162
+ dataLayer: [],
163
+ network: ['widget.intercom.io', 'api.intercom.io']
164
+ },
165
+
166
+ // Drift
167
+ {
168
+ id: 'drift',
169
+ name: 'Drift',
170
+ category: 'preferences',
171
+ scripts: ['js.driftt.com/include/'],
172
+ globals: ['drift'],
173
+ cookies: ['driftt_aid'],
174
+ dataLayer: [],
175
+ network: ['js.driftt.com']
176
+ },
177
+
178
+ // Amplitude
179
+ {
180
+ id: 'amplitude',
181
+ name: 'Amplitude',
182
+ category: 'analytics',
183
+ scripts: ['cdn.amplitude.com'],
184
+ globals: ['amplitude'],
185
+ cookies: ['amplitude_'],
186
+ dataLayer: [],
187
+ network: ['cdn.amplitude.com', 'api.amplitude.com']
188
+ },
189
+
190
+ // Heap Analytics
191
+ {
192
+ id: 'heap',
193
+ name: 'Heap Analytics',
194
+ category: 'analytics',
195
+ scripts: ['cdn.heapanalytics.com'],
196
+ globals: ['heap'],
197
+ cookies: ['_hp2_'],
198
+ dataLayer: [],
199
+ network: ['cdn.heapanalytics.com', 'heapanalytics.com']
200
+ }
201
+ ]
202
+
203
+ defaultPatterns.forEach(pattern => this.register(pattern))
204
+ }
205
+
206
+ /**
207
+ * Register new tracker pattern (extensible!)
208
+ * @param {Object} pattern - Tracker pattern
209
+ */
210
+ register(pattern) {
211
+ if (!pattern.id || !pattern.name) return
212
+
213
+ // Set defaults
214
+ const fullPattern = {
215
+ scripts: [],
216
+ globals: [],
217
+ cookies: [],
218
+ dataLayer: [],
219
+ network: [],
220
+ ...pattern
221
+ }
222
+
223
+ this.patterns.set(pattern.id, fullPattern)
224
+ }
225
+
226
+ /**
227
+ * Get pattern by ID
228
+ * @param {string} id - Pattern ID
229
+ * @returns {Object|null}
230
+ */
231
+ get(id) {
232
+ return this.patterns.get(id) || null
233
+ }
234
+
235
+ /**
236
+ * Get all patterns
237
+ * @returns {Array}
238
+ */
239
+ getAll() {
240
+ return Array.from(this.patterns.values())
241
+ }
242
+
243
+ /**
244
+ * Get patterns by category
245
+ * @param {string} category - Category name
246
+ * @returns {Array}
247
+ */
248
+ getByCategory(category) {
249
+ return this.getAll().filter(p => p.category === category)
250
+ }
251
+
252
+ /**
253
+ * Check if pattern exists
254
+ * @param {string} id - Pattern ID
255
+ * @returns {boolean}
256
+ */
257
+ has(id) {
258
+ return this.patterns.has(id)
259
+ }
260
+
261
+ /**
262
+ * Remove pattern
263
+ * @param {string} id - Pattern ID
264
+ */
265
+ remove(id) {
266
+ this.patterns.delete(id)
267
+ }
268
+
269
+ /**
270
+ * Clear all patterns
271
+ */
272
+ clear() {
273
+ this.patterns.clear()
274
+ }
275
+
276
+ /**
277
+ * Get count
278
+ * @returns {number}
279
+ */
280
+ count() {
281
+ return this.patterns.size
282
+ }
283
+ }
284
+
285
+ // Singleton instance
286
+ const patternRegistry = new TrackerPatternRegistry()
287
+
288
+ // Export registry and patterns
289
+ export const TRACKER_PATTERNS = patternRegistry.getAll()
290
+
291
+ export function getTrackerPattern(id) {
292
+ return patternRegistry.get(id)
293
+ }
294
+
295
+ export function getTrackerPatternsByCategory(category) {
296
+ return patternRegistry.getByCategory(category)
297
+ }
298
+
299
+ export function registerTrackerPattern(pattern) {
300
+ return patternRegistry.register(pattern)
301
+ }
302
+
303
+ export function getAllTrackerPatterns() {
304
+ return patternRegistry.getAll()
305
+ }
306
+
307
+ export { patternRegistry as TrackerPatternRegistry }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * TrackerRegistry — stores and manages detected trackers
3
+ * @module TrackerRegistry
4
+ */
5
+
6
+ export class TrackerRegistry {
7
+ constructor() {
8
+ this.trackers = new Map() // id -> tracker
9
+ this.categories = new Map() // category -> [trackers]
10
+ }
11
+
12
+ /**
13
+ * Add tracker to registry
14
+ * @param {Object} tracker - Tracker info
15
+ */
16
+ add(tracker) {
17
+ if (!tracker.id) return
18
+
19
+ // Store by id
20
+ this.trackers.set(tracker.id, tracker)
21
+
22
+ // Store by category
23
+ if (tracker.category) {
24
+ if (!this.categories.has(tracker.category)) {
25
+ this.categories.set(tracker.category, [])
26
+ }
27
+
28
+ const categoryTrackers = this.categories.get(tracker.category)
29
+ if (!categoryTrackers.find(t => t.id === tracker.id)) {
30
+ categoryTrackers.push(tracker)
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Add multiple trackers
37
+ * @param {Array} trackers - Array of trackers
38
+ */
39
+ addMany(trackers) {
40
+ trackers.forEach(tracker => this.add(tracker))
41
+ }
42
+
43
+ /**
44
+ * Get tracker by id
45
+ * @param {string} id - Tracker id
46
+ * @returns {Object|null}
47
+ */
48
+ get(id) {
49
+ return this.trackers.get(id) || null
50
+ }
51
+
52
+ /**
53
+ * Get all trackers
54
+ * @returns {Array}
55
+ */
56
+ getAll() {
57
+ return Array.from(this.trackers.values())
58
+ }
59
+
60
+ /**
61
+ * Get trackers by category
62
+ * @param {string} category - Category name
63
+ * @returns {Array}
64
+ */
65
+ getByCategory(category) {
66
+ return this.categories.get(category) || []
67
+ }
68
+
69
+ /**
70
+ * Get all categories
71
+ * @returns {Array}
72
+ */
73
+ getCategories() {
74
+ return Array.from(this.categories.keys())
75
+ }
76
+
77
+ /**
78
+ * Check if tracker exists
79
+ * @param {string} id - Tracker id
80
+ * @returns {boolean}
81
+ */
82
+ has(id) {
83
+ return this.trackers.has(id)
84
+ }
85
+
86
+ /**
87
+ * Remove tracker
88
+ * @param {string} id - Tracker id
89
+ */
90
+ remove(id) {
91
+ const tracker = this.trackers.get(id)
92
+ if (!tracker) return
93
+
94
+ // Remove from main map
95
+ this.trackers.delete(id)
96
+
97
+ // Remove from category map
98
+ if (tracker.category) {
99
+ const categoryTrackers = this.categories.get(tracker.category)
100
+ if (categoryTrackers) {
101
+ const index = categoryTrackers.findIndex(t => t.id === id)
102
+ if (index !== -1) {
103
+ categoryTrackers.splice(index, 1)
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Clear all trackers
111
+ */
112
+ clear() {
113
+ this.trackers.clear()
114
+ this.categories.clear()
115
+ }
116
+
117
+ /**
118
+ * Get registry stats
119
+ * @returns {Object}
120
+ */
121
+ getStats() {
122
+ const all = this.getAll()
123
+ return {
124
+ total: all.length,
125
+ byCategory: Object.fromEntries(
126
+ Array.from(this.categories.entries()).map(([cat, trackers]) => [
127
+ cat,
128
+ {
129
+ count: trackers.length,
130
+ trackers: trackers.map(t => t.name)
131
+ }
132
+ ])
133
+ )
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Get count of trackers
139
+ * @returns {number}
140
+ */
141
+ count() {
142
+ return this.trackers.size
143
+ }
144
+
145
+ /**
146
+ * Export trackers as array
147
+ * @returns {Array}
148
+ */
149
+ toArray() {
150
+ return this.getAll()
151
+ }
152
+
153
+ /**
154
+ * Get suggested consent categories based on detected trackers
155
+ * @returns {Object}
156
+ */
157
+ getSuggestedCategories() {
158
+ const suggestions = {}
159
+
160
+ this.getAll().forEach(tracker => {
161
+ if (!suggestions[tracker.category]) {
162
+ suggestions[tracker.category] = {
163
+ required: tracker.category === 'necessary',
164
+ trackers: []
165
+ }
166
+ }
167
+ suggestions[tracker.category].trackers.push(tracker.name)
168
+ })
169
+
170
+ return suggestions
171
+ }
172
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Tracker Detection System — Auto-detect trackers on the page
3
+ * @module trackers
4
+ */
5
+
6
+ export { TrackerDetector } from './TrackerDetector.js'
7
+ export { TrackerRegistry } from './TrackerRegistry.js'
8
+ export {
9
+ TRACKER_PATTERNS,
10
+ getTrackerPattern,
11
+ getTrackerPatternsByCategory,
12
+ registerTrackerPattern,
13
+ getAllTrackerPatterns,
14
+ TrackerPatternRegistry
15
+ } from './TrackerPatterns.js'
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Cookie utilities
3
+ */
4
+
5
+ export function getCookie(name) {
6
+ if (typeof document === 'undefined') return null
7
+ const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'))
8
+ return match ? decodeURIComponent(match[2]) : null
9
+ }
10
+
11
+ export function setCookie(name, value, days, options = {}) {
12
+ if (typeof document === 'undefined') return
13
+
14
+ const expires = new Date(Date.now() + days * 864e5).toUTCString()
15
+ const path = options.path || '/'
16
+ const sameSite = options.sameSite || 'Lax'
17
+ const secure = options.secure ? '; Secure' : ''
18
+ const domain = options.domain ? `; Domain=${options.domain}` : ''
19
+
20
+ document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=${path}; SameSite=${sameSite}${secure}${domain}`
21
+ }
22
+
23
+ export function deleteCookie(name, options = {}) {
24
+ setCookie(name, '', -1, options)
25
+ }
26
+
27
+ export function getAllCookies() {
28
+ if (typeof document === 'undefined') return {}
29
+
30
+ return document.cookie.split(';').reduce((acc, cookie) => {
31
+ const [name, value] = cookie.trim().split('=')
32
+ if (name) {
33
+ acc[name] = decodeURIComponent(value || '')
34
+ }
35
+ return acc
36
+ }, {})
37
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * DOM utilities
3
+ */
4
+
5
+ export function getFocusableElements(container) {
6
+ if (!container) return []
7
+
8
+ const selector = [
9
+ 'a[href]',
10
+ 'button:not([disabled])',
11
+ 'textarea:not([disabled])',
12
+ 'input:not([disabled])',
13
+ 'select:not([disabled])',
14
+ '[tabindex]:not([tabindex="-1"])'
15
+ ].join(',')
16
+
17
+ return Array.from(container.querySelectorAll(selector))
18
+ }
19
+
20
+ export function trapFocus(container, event) {
21
+ const focusableElements = getFocusableElements(container)
22
+ if (focusableElements.length === 0) return
23
+
24
+ const firstElement = focusableElements[0]
25
+ const lastElement = focusableElements[focusableElements.length - 1]
26
+
27
+ if (event.key === 'Tab') {
28
+ if (event.shiftKey && document.activeElement === firstElement) {
29
+ event.preventDefault()
30
+ lastElement.focus()
31
+ } else if (!event.shiftKey && document.activeElement === lastElement) {
32
+ event.preventDefault()
33
+ firstElement.focus()
34
+ }
35
+ }
36
+ }
37
+
38
+ export function lockBodyScroll() {
39
+ if (typeof document === 'undefined') return
40
+ document.body.style.overflow = 'hidden'
41
+ }
42
+
43
+ export function unlockBodyScroll() {
44
+ if (typeof document === 'undefined') return
45
+ document.body.style.overflow = ''
46
+ }
47
+
48
+ export function onEscape(callback) {
49
+ const handler = (event) => {
50
+ if (event.key === 'Escape' || event.keyCode === 27) {
51
+ callback(event)
52
+ }
53
+ }
54
+
55
+ document.addEventListener('keydown', handler)
56
+
57
+ return () => document.removeEventListener('keydown', handler)
58
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * General helper utilities
3
+ */
4
+
5
+ export function generateUUID() {
6
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
7
+ const r = Math.random() * 16 | 0
8
+ const v = c === 'x' ? r : (r & 0x3 | 0x8)
9
+ return v.toString(16)
10
+ })
11
+ }
12
+
13
+ export function debounce(fn, delay) {
14
+ let timeoutId
15
+ return function (...args) {
16
+ clearTimeout(timeoutId)
17
+ timeoutId = setTimeout(() => fn.apply(this, args), delay)
18
+ }
19
+ }
20
+
21
+ export function throttle(fn, limit) {
22
+ let inThrottle
23
+ return function (...args) {
24
+ if (!inThrottle) {
25
+ fn.apply(this, args)
26
+ inThrottle = true
27
+ setTimeout(() => inThrottle = false, limit)
28
+ }
29
+ }
30
+ }
31
+
32
+ export function deepMerge(target, source) {
33
+ const output = { ...target }
34
+
35
+ if (isObject(target) && isObject(source)) {
36
+ Object.keys(source).forEach(key => {
37
+ if (isObject(source[key])) {
38
+ if (!(key in target)) {
39
+ output[key] = source[key]
40
+ } else {
41
+ output[key] = deepMerge(target[key], source[key])
42
+ }
43
+ } else {
44
+ output[key] = source[key]
45
+ }
46
+ })
47
+ }
48
+
49
+ return output
50
+ }
51
+
52
+ function isObject(item) {
53
+ return item && typeof item === 'object' && !Array.isArray(item)
54
+ }
55
+
56
+ export function isSSR() {
57
+ return typeof window === 'undefined'
58
+ }
59
+
60
+ export function isMobile() {
61
+ if (isSSR()) return false
62
+ return window.innerWidth <= 768
63
+ }
64
+
65
+ export function getRegionFromTimezone() {
66
+ if (isSSR()) return null
67
+
68
+ try {
69
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
70
+
71
+ // EU timezones
72
+ const euTimezones = [
73
+ 'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Europe/Madrid',
74
+ 'Europe/Rome', 'Europe/Amsterdam', 'Europe/Brussels', 'Europe/Vienna',
75
+ 'Europe/Stockholm', 'Europe/Copenhagen', 'Europe/Helsinki', 'Europe/Athens',
76
+ 'Europe/Prague', 'Europe/Warsaw', 'Europe/Budapest', 'Europe/Bucharest'
77
+ ]
78
+
79
+ if (euTimezones.some(tz => timezone.startsWith(tz.split('/')[0]))) {
80
+ return 'EU'
81
+ }
82
+
83
+ if (timezone.startsWith('America/')) return 'US'
84
+
85
+ return 'OTHER'
86
+ } catch (e) {
87
+ return null
88
+ }
89
+ }