@freshjuice/zest 0.1.0 → 1.0.0

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.
Files changed (76) hide show
  1. package/README.md +46 -0
  2. package/dist/zest.de.js +104 -1
  3. package/dist/zest.de.js.map +1 -1
  4. package/dist/zest.de.min.js +1 -1
  5. package/dist/zest.en.js +104 -1
  6. package/dist/zest.en.js.map +1 -1
  7. package/dist/zest.en.min.js +1 -1
  8. package/dist/zest.es.js +104 -1
  9. package/dist/zest.es.js.map +1 -1
  10. package/dist/zest.es.min.js +1 -1
  11. package/dist/zest.esm.js +104 -1
  12. package/dist/zest.esm.js.map +1 -1
  13. package/dist/zest.esm.min.js +1 -1
  14. package/dist/zest.fr.js +104 -1
  15. package/dist/zest.fr.js.map +1 -1
  16. package/dist/zest.fr.min.js +1 -1
  17. package/dist/zest.it.js +104 -1
  18. package/dist/zest.it.js.map +1 -1
  19. package/dist/zest.it.min.js +1 -1
  20. package/dist/zest.ja.js +104 -1
  21. package/dist/zest.ja.js.map +1 -1
  22. package/dist/zest.ja.min.js +1 -1
  23. package/dist/zest.js +104 -1
  24. package/dist/zest.js.map +1 -1
  25. package/dist/zest.min.js +1 -1
  26. package/dist/zest.nl.js +104 -1
  27. package/dist/zest.nl.js.map +1 -1
  28. package/dist/zest.nl.min.js +1 -1
  29. package/dist/zest.pl.js +104 -1
  30. package/dist/zest.pl.js.map +1 -1
  31. package/dist/zest.pl.min.js +1 -1
  32. package/dist/zest.pt.js +104 -1
  33. package/dist/zest.pt.js.map +1 -1
  34. package/dist/zest.pt.min.js +1 -1
  35. package/dist/zest.ru.js +104 -1
  36. package/dist/zest.ru.js.map +1 -1
  37. package/dist/zest.ru.min.js +1 -1
  38. package/dist/zest.uk.js +104 -1
  39. package/dist/zest.uk.js.map +1 -1
  40. package/dist/zest.uk.min.js +1 -1
  41. package/dist/zest.zh.js +104 -1
  42. package/dist/zest.zh.js.map +1 -1
  43. package/dist/zest.zh.min.js +1 -1
  44. package/package.json +5 -4
  45. package/src/api/public-api.js +97 -0
  46. package/src/config/defaults.js +150 -0
  47. package/src/config/parser.js +104 -0
  48. package/src/core/categories.js +52 -0
  49. package/src/core/cookie-interceptor.js +116 -0
  50. package/src/core/dnt.js +56 -0
  51. package/src/core/known-trackers.js +168 -0
  52. package/src/core/pattern-matcher.js +96 -0
  53. package/src/core/script-blocker.js +308 -0
  54. package/src/core/storage-interceptor.js +169 -0
  55. package/src/i18n/lang-en.js +54 -0
  56. package/src/i18n/single/lang-de.js +55 -0
  57. package/src/i18n/single/lang-en.js +55 -0
  58. package/src/i18n/single/lang-es.js +55 -0
  59. package/src/i18n/single/lang-fr.js +55 -0
  60. package/src/i18n/single/lang-it.js +55 -0
  61. package/src/i18n/single/lang-ja.js +55 -0
  62. package/src/i18n/single/lang-nl.js +55 -0
  63. package/src/i18n/single/lang-pl.js +55 -0
  64. package/src/i18n/single/lang-pt.js +55 -0
  65. package/src/i18n/single/lang-ru.js +55 -0
  66. package/src/i18n/single/lang-uk.js +55 -0
  67. package/src/i18n/single/lang-zh.js +55 -0
  68. package/src/i18n/translations.js +546 -0
  69. package/src/index.js +377 -0
  70. package/src/integrations/consent-signals.js +71 -0
  71. package/src/storage/consent-store.js +177 -0
  72. package/src/storage/events.js +84 -0
  73. package/src/ui/banner.js +130 -0
  74. package/src/ui/modal.js +211 -0
  75. package/src/ui/styles.js +498 -0
  76. package/src/ui/widget.js +103 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Cookie Interceptor - Intercepts document.cookie operations
3
+ */
4
+
5
+ import { getCategoryForName, parseCookieName } from './pattern-matcher.js';
6
+
7
+ // Store original descriptor
8
+ let originalCookieDescriptor = null;
9
+
10
+ // Queue for blocked cookies
11
+ const cookieQueue = [];
12
+
13
+ // Reference to consent checker function
14
+ let checkConsent = () => false;
15
+
16
+ /**
17
+ * Set the consent checker function
18
+ */
19
+ export function setConsentChecker(fn) {
20
+ checkConsent = fn;
21
+ }
22
+
23
+ /**
24
+ * Get the original cookie descriptor
25
+ */
26
+ export function getOriginalCookieDescriptor() {
27
+ return originalCookieDescriptor;
28
+ }
29
+
30
+ /**
31
+ * Get queued cookies
32
+ */
33
+ export function getCookieQueue() {
34
+ return [...cookieQueue];
35
+ }
36
+
37
+ /**
38
+ * Clear the cookie queue
39
+ */
40
+ export function clearCookieQueue() {
41
+ cookieQueue.length = 0;
42
+ }
43
+
44
+ /**
45
+ * Replay queued cookies for allowed categories
46
+ */
47
+ export function replayCookies(allowedCategories) {
48
+ const remaining = [];
49
+
50
+ for (const item of cookieQueue) {
51
+ if (allowedCategories.includes(item.category)) {
52
+ // Set the cookie using original setter
53
+ if (originalCookieDescriptor?.set) {
54
+ originalCookieDescriptor.set.call(document, item.value);
55
+ }
56
+ } else {
57
+ remaining.push(item);
58
+ }
59
+ }
60
+
61
+ cookieQueue.length = 0;
62
+ cookieQueue.push(...remaining);
63
+ }
64
+
65
+ /**
66
+ * Start intercepting cookies
67
+ */
68
+ export function interceptCookies() {
69
+ // Store original
70
+ originalCookieDescriptor = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie');
71
+
72
+ if (!originalCookieDescriptor) {
73
+ console.warn('[Zest] Could not get cookie descriptor');
74
+ return false;
75
+ }
76
+
77
+ Object.defineProperty(document, 'cookie', {
78
+ get() {
79
+ // Always allow reading
80
+ return originalCookieDescriptor.get.call(document);
81
+ },
82
+ set(value) {
83
+ const name = parseCookieName(value);
84
+ if (!name) {
85
+ return;
86
+ }
87
+
88
+ const category = getCategoryForName(name);
89
+
90
+ if (checkConsent(category)) {
91
+ // Consent given - set cookie
92
+ originalCookieDescriptor.set.call(document, value);
93
+ } else {
94
+ // No consent - queue for later
95
+ cookieQueue.push({
96
+ value,
97
+ name,
98
+ category,
99
+ timestamp: Date.now()
100
+ });
101
+ }
102
+ },
103
+ configurable: true
104
+ });
105
+
106
+ return true;
107
+ }
108
+
109
+ /**
110
+ * Restore original cookie behavior
111
+ */
112
+ export function restoreCookies() {
113
+ if (originalCookieDescriptor) {
114
+ Object.defineProperty(document, 'cookie', originalCookieDescriptor);
115
+ }
116
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Do Not Track (DNT) Detection
3
+ *
4
+ * Detects browser DNT/GPC signals for privacy compliance
5
+ */
6
+
7
+ /**
8
+ * Check if Do Not Track is enabled
9
+ * Checks both DNT header and Global Privacy Control (GPC)
10
+ */
11
+ export function isDoNotTrackEnabled() {
12
+ if (typeof navigator === 'undefined') {
13
+ return false;
14
+ }
15
+
16
+ // Check DNT (Do Not Track)
17
+ // Values: "1" = enabled, "0" = disabled, null/undefined = not set
18
+ const dnt = navigator.doNotTrack ||
19
+ window.doNotTrack ||
20
+ navigator.msDoNotTrack;
21
+
22
+ if (dnt === '1' || dnt === 'yes' || dnt === true) {
23
+ return true;
24
+ }
25
+
26
+ // Check GPC (Global Privacy Control) - newer standard
27
+ // https://globalprivacycontrol.org/
28
+ if (navigator.globalPrivacyControl === true) {
29
+ return true;
30
+ }
31
+
32
+ return false;
33
+ }
34
+
35
+ /**
36
+ * Get DNT signal details for logging/debugging
37
+ */
38
+ export function getDNTDetails() {
39
+ if (typeof navigator === 'undefined') {
40
+ return { enabled: false, source: null };
41
+ }
42
+
43
+ const dnt = navigator.doNotTrack ||
44
+ window.doNotTrack ||
45
+ navigator.msDoNotTrack;
46
+
47
+ if (dnt === '1' || dnt === 'yes' || dnt === true) {
48
+ return { enabled: true, source: 'dnt' };
49
+ }
50
+
51
+ if (navigator.globalPrivacyControl === true) {
52
+ return { enabled: true, source: 'gpc' };
53
+ }
54
+
55
+ return { enabled: false, source: null };
56
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Known Trackers - Lists of known tracking script domains by category
3
+ */
4
+
5
+ /**
6
+ * Safe mode - Major, well-known trackers only
7
+ */
8
+ export const SAFE_TRACKERS = {
9
+ analytics: [
10
+ 'google-analytics.com',
11
+ 'www.google-analytics.com',
12
+ 'analytics.google.com',
13
+ 'googletagmanager.com',
14
+ 'www.googletagmanager.com',
15
+ 'plausible.io',
16
+ 'cloudflareinsights.com',
17
+ 'static.cloudflareinsights.com'
18
+ ],
19
+ marketing: [
20
+ 'connect.facebook.net',
21
+ 'www.facebook.com/tr',
22
+ 'ads.google.com',
23
+ 'www.googleadservices.com',
24
+ 'googleads.g.doubleclick.net',
25
+ 'pagead2.googlesyndication.com'
26
+ ]
27
+ };
28
+
29
+ /**
30
+ * Strict mode - Extended list including less common trackers
31
+ */
32
+ export const STRICT_TRACKERS = {
33
+ analytics: [
34
+ ...SAFE_TRACKERS.analytics,
35
+ 'analytics.tiktok.com',
36
+ 'matomo.', // partial match
37
+ 'hotjar.com',
38
+ 'static.hotjar.com',
39
+ 'script.hotjar.com',
40
+ 'clarity.ms',
41
+ 'www.clarity.ms',
42
+ 'heapanalytics.com',
43
+ 'cdn.heapanalytics.com',
44
+ 'mixpanel.com',
45
+ 'cdn.mxpnl.com',
46
+ 'segment.com',
47
+ 'cdn.segment.com',
48
+ 'api.segment.io',
49
+ 'fullstory.com',
50
+ 'rs.fullstory.com',
51
+ 'amplitude.com',
52
+ 'cdn.amplitude.com',
53
+ 'mouseflow.com',
54
+ 'cdn.mouseflow.com',
55
+ 'luckyorange.com',
56
+ 'cdn.luckyorange.net',
57
+ 'crazyegg.com',
58
+ 'script.crazyegg.com'
59
+ ],
60
+ marketing: [
61
+ ...SAFE_TRACKERS.marketing,
62
+ 'snap.licdn.com',
63
+ 'px.ads.linkedin.com',
64
+ 'ads.linkedin.com',
65
+ 'analytics.twitter.com',
66
+ 'static.ads-twitter.com',
67
+ 't.co',
68
+ 'analytics.tiktok.com',
69
+ 'ads.tiktok.com',
70
+ 'sc-static.net', // Snapchat
71
+ 'tr.snapchat.com',
72
+ 'ct.pinterest.com',
73
+ 'pintrk.com',
74
+ 's.pinimg.com',
75
+ 'widgets.pinterest.com',
76
+ 'bat.bing.com',
77
+ 'ads.yahoo.com',
78
+ 'sp.analytics.yahoo.com',
79
+ 'amazon-adsystem.com',
80
+ 'z-na.amazon-adsystem.com',
81
+ 'criteo.com',
82
+ 'static.criteo.net',
83
+ 'dis.criteo.com',
84
+ 'taboola.com',
85
+ 'cdn.taboola.com',
86
+ 'trc.taboola.com',
87
+ 'outbrain.com',
88
+ 'widgets.outbrain.com',
89
+ 'adroll.com',
90
+ 's.adroll.com'
91
+ ],
92
+ functional: [
93
+ 'cdn.onesignal.com',
94
+ 'onesignal.com',
95
+ 'pusher.com',
96
+ 'js.pusher.com',
97
+ 'intercom.io',
98
+ 'widget.intercom.io',
99
+ 'js.intercomcdn.com',
100
+ 'crisp.chat',
101
+ 'client.crisp.chat',
102
+ 'cdn.livechatinc.com',
103
+ 'livechatinc.com',
104
+ 'tawk.to',
105
+ 'embed.tawk.to',
106
+ 'zendesk.com',
107
+ 'static.zdassets.com'
108
+ ]
109
+ };
110
+
111
+ /**
112
+ * Check if a URL matches any tracker in the list
113
+ */
114
+ export function matchesTrackerList(url, trackerList) {
115
+ try {
116
+ const urlObj = new URL(url);
117
+ const hostname = urlObj.hostname.toLowerCase();
118
+ const fullUrl = url.toLowerCase();
119
+
120
+ for (const domain of trackerList) {
121
+ // Support partial matches (e.g., "matomo." matches "analytics.matomo.cloud")
122
+ if (domain.endsWith('.')) {
123
+ if (hostname.includes(domain.slice(0, -1))) {
124
+ return true;
125
+ }
126
+ } else if (hostname === domain || hostname.endsWith('.' + domain)) {
127
+ return true;
128
+ } else if (fullUrl.includes(domain)) {
129
+ return true;
130
+ }
131
+ }
132
+ } catch (e) {
133
+ // Invalid URL
134
+ }
135
+ return false;
136
+ }
137
+
138
+ /**
139
+ * Get category for a script URL based on tracker lists
140
+ */
141
+ export function getCategoryForScript(url, mode = 'safe') {
142
+ const trackers = mode === 'strict' ? STRICT_TRACKERS : SAFE_TRACKERS;
143
+
144
+ for (const [category, domains] of Object.entries(trackers)) {
145
+ if (matchesTrackerList(url, domains)) {
146
+ return category;
147
+ }
148
+ }
149
+
150
+ return null;
151
+ }
152
+
153
+ /**
154
+ * Check if URL is third-party (different domain)
155
+ */
156
+ export function isThirdParty(url) {
157
+ try {
158
+ const scriptHost = new URL(url).hostname;
159
+ const pageHost = window.location.hostname;
160
+
161
+ // Remove www. for comparison
162
+ const normalizeHost = (h) => h.replace(/^www\./, '');
163
+
164
+ return normalizeHost(scriptHost) !== normalizeHost(pageHost);
165
+ } catch (e) {
166
+ return false;
167
+ }
168
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Pattern Matcher - Categorizes cookies and storage keys by pattern
3
+ */
4
+
5
+ /**
6
+ * Default patterns for each category
7
+ */
8
+ export const DEFAULT_PATTERNS = {
9
+ essential: [
10
+ /^zest_/,
11
+ /^csrf/i,
12
+ /^xsrf/i,
13
+ /^session/i,
14
+ /^__host-/i,
15
+ /^__secure-/i
16
+ ],
17
+ functional: [
18
+ /^lang/i,
19
+ /^locale/i,
20
+ /^theme/i,
21
+ /^preferences/i,
22
+ /^ui_/i
23
+ ],
24
+ analytics: [
25
+ /^_ga/,
26
+ /^_gid/,
27
+ /^_gat/,
28
+ /^_utm/,
29
+ /^__utm/,
30
+ /^plausible/i,
31
+ /^_pk_/,
32
+ /^matomo/i,
33
+ /^_hj/,
34
+ /^ajs_/
35
+ ],
36
+ marketing: [
37
+ /^_fbp/,
38
+ /^_fbc/,
39
+ /^_gcl/,
40
+ /^_ttp/,
41
+ /^ads/i,
42
+ /^doubleclick/i,
43
+ /^__gads/,
44
+ /^__gpi/,
45
+ /^_pin_/,
46
+ /^li_/
47
+ ]
48
+ };
49
+
50
+ let patterns = { ...DEFAULT_PATTERNS };
51
+
52
+ /**
53
+ * Set custom patterns
54
+ */
55
+ export function setPatterns(customPatterns) {
56
+ patterns = { ...DEFAULT_PATTERNS };
57
+ for (const [category, regexList] of Object.entries(customPatterns)) {
58
+ if (Array.isArray(regexList)) {
59
+ patterns[category] = regexList.map(p =>
60
+ p instanceof RegExp ? p : new RegExp(p)
61
+ );
62
+ }
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Get current patterns
68
+ */
69
+ export function getPatterns() {
70
+ return { ...patterns };
71
+ }
72
+
73
+ /**
74
+ * Determine category for a cookie/storage key name
75
+ * @param {string} name - Cookie or storage key name
76
+ * @returns {string} Category ID (defaults to 'marketing' for unknown)
77
+ */
78
+ export function getCategoryForName(name) {
79
+ for (const [category, regexList] of Object.entries(patterns)) {
80
+ if (regexList.some(regex => regex.test(name))) {
81
+ return category;
82
+ }
83
+ }
84
+ // Unknown items default to marketing (strictest)
85
+ return 'marketing';
86
+ }
87
+
88
+ /**
89
+ * Parse cookie string to extract name
90
+ * @param {string} cookieString - Full cookie string (e.g., "name=value; path=/")
91
+ * @returns {string|null} Cookie name or null
92
+ */
93
+ export function parseCookieName(cookieString) {
94
+ const match = cookieString.match(/^([^=]+)/);
95
+ return match ? match[1].trim() : null;
96
+ }