@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.
- 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/core/index.js +4 -4
- 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
- package/src/vue/CookieConsent.vue +4 -12
|
@@ -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
|
+
}
|
package/src/utils/dom.js
ADDED
|
@@ -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
|
+
}
|