@freshjuice/zest 0.1.0 → 2.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.
- package/README.md +216 -70
- package/dist/zest.de.js +776 -286
- package/dist/zest.de.js.map +1 -1
- package/dist/zest.de.min.js +1 -1
- package/dist/zest.en.js +776 -286
- package/dist/zest.en.js.map +1 -1
- package/dist/zest.en.min.js +1 -1
- package/dist/zest.es.js +776 -286
- package/dist/zest.es.js.map +1 -1
- package/dist/zest.es.min.js +1 -1
- package/dist/zest.esm.js +776 -286
- package/dist/zest.esm.js.map +1 -1
- package/dist/zest.esm.min.js +1 -1
- package/dist/zest.fr.js +776 -286
- package/dist/zest.fr.js.map +1 -1
- package/dist/zest.fr.min.js +1 -1
- package/dist/zest.headless.esm.js +2299 -0
- package/dist/zest.headless.esm.js.map +1 -0
- package/dist/zest.headless.esm.min.js +1 -0
- package/dist/zest.it.js +776 -286
- package/dist/zest.it.js.map +1 -1
- package/dist/zest.it.min.js +1 -1
- package/dist/zest.ja.js +776 -286
- package/dist/zest.ja.js.map +1 -1
- package/dist/zest.ja.min.js +1 -1
- package/dist/zest.js +776 -286
- package/dist/zest.js.map +1 -1
- package/dist/zest.min.js +1 -1
- package/dist/zest.nl.js +776 -286
- package/dist/zest.nl.js.map +1 -1
- package/dist/zest.nl.min.js +1 -1
- package/dist/zest.pl.js +776 -286
- package/dist/zest.pl.js.map +1 -1
- package/dist/zest.pl.min.js +1 -1
- package/dist/zest.pt.js +776 -286
- package/dist/zest.pt.js.map +1 -1
- package/dist/zest.pt.min.js +1 -1
- package/dist/zest.ru.js +776 -286
- package/dist/zest.ru.js.map +1 -1
- package/dist/zest.ru.min.js +1 -1
- package/dist/zest.uk.js +776 -286
- package/dist/zest.uk.js.map +1 -1
- package/dist/zest.uk.min.js +1 -1
- package/dist/zest.zh.js +776 -286
- package/dist/zest.zh.js.map +1 -1
- package/dist/zest.zh.min.js +1 -1
- package/package.json +17 -4
- package/src/api/public-api.js +97 -0
- package/src/config/defaults.js +150 -0
- package/src/config/parser.js +104 -0
- package/src/core/categories.js +52 -0
- package/src/core/cookie-interceptor.js +131 -0
- package/src/core/dnt.js +56 -0
- package/src/core/known-trackers.js +195 -0
- package/src/core/pattern-matcher.js +111 -0
- package/src/core/script-blocker.js +314 -0
- package/src/core/security.js +204 -0
- package/src/core/storage-interceptor.js +173 -0
- package/src/core-lifecycle.js +192 -0
- package/src/headless.js +133 -0
- package/src/i18n/lang-en.js +54 -0
- package/src/i18n/single/lang-de.js +55 -0
- package/src/i18n/single/lang-en.js +55 -0
- package/src/i18n/single/lang-es.js +55 -0
- package/src/i18n/single/lang-fr.js +55 -0
- package/src/i18n/single/lang-it.js +55 -0
- package/src/i18n/single/lang-ja.js +55 -0
- package/src/i18n/single/lang-nl.js +55 -0
- package/src/i18n/single/lang-pl.js +55 -0
- package/src/i18n/single/lang-pt.js +55 -0
- package/src/i18n/single/lang-ru.js +55 -0
- package/src/i18n/single/lang-uk.js +55 -0
- package/src/i18n/single/lang-zh.js +55 -0
- package/src/i18n/translations.js +546 -0
- package/src/index.js +266 -0
- package/src/integrations/consent-signals.js +71 -0
- package/src/storage/consent-store.js +201 -0
- package/src/storage/events.js +84 -0
- package/src/ui/banner.js +134 -0
- package/src/ui/modal.js +215 -0
- package/src/ui/styles.js +519 -0
- package/src/ui/widget.js +105 -0
package/README.md
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# Zest 🍋
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@freshjuice/zest)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://github.com/freshjuice-dev/zest/stargazers)
|
|
6
|
+
[](https://github.com/freshjuice-dev/zest/network/members)
|
|
7
|
+
|
|
3
8
|
A lightweight cookie consent toolkit for GDPR/CCPA compliance.
|
|
4
9
|
|
|
5
|
-
- **Lightweight**
|
|
6
|
-
- **Zero dependencies**
|
|
7
|
-
- **Shadow DOM**
|
|
8
|
-
- **
|
|
9
|
-
- **Privacy-first**
|
|
10
|
+
- **Lightweight** — ~9KB gzipped (single language) / ~16KB (all 12 languages) / ~11KB (headless)
|
|
11
|
+
- **Zero dependencies** — Vanilla JavaScript
|
|
12
|
+
- **Shadow DOM** — Styles isolated from your site
|
|
13
|
+
- **Headless mode** — Bring your own UI & CSS, use only the consent engine
|
|
14
|
+
- **Privacy-first** — Respects Do Not Track / Global Privacy Control
|
|
15
|
+
- **Security-hardened** — XSS-safe templating, URL/color/regex validation, locked interceptors
|
|
10
16
|
|
|
11
17
|
## Quick Start
|
|
12
18
|
|
|
@@ -18,7 +24,7 @@ A lightweight cookie consent toolkit for GDPR/CCPA compliance.
|
|
|
18
24
|
<script src="https://cdn.jsdelivr.net/npm/@freshjuice/zest"></script>
|
|
19
25
|
```
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
With configuration:
|
|
22
28
|
|
|
23
29
|
```html
|
|
24
30
|
<script>
|
|
@@ -32,6 +38,23 @@ Or with configuration:
|
|
|
32
38
|
<script src="https://unpkg.com/@freshjuice/zest"></script>
|
|
33
39
|
```
|
|
34
40
|
|
|
41
|
+
As an npm dependency:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import Zest from '@freshjuice/zest';
|
|
45
|
+
|
|
46
|
+
Zest.init({ mode: 'safe', policyUrl: '/privacy' });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Two build flavors
|
|
50
|
+
|
|
51
|
+
| Entry | What you get | Min / Gzip |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `@freshjuice/zest` | Consent engine **+ Shadow DOM UI** (banner, modal, widget) | ~50 KB / **~16 KB** |
|
|
54
|
+
| `@freshjuice/zest/headless` | Consent engine only, **no UI / no CSS** — you build the UI | ~31 KB / **~11 KB** |
|
|
55
|
+
|
|
56
|
+
Use **headless** when you want full control over markup and styling.
|
|
57
|
+
|
|
35
58
|
## Configuration
|
|
36
59
|
|
|
37
60
|
### Via `window.ZestConfig`
|
|
@@ -44,10 +67,10 @@ window.ZestConfig = {
|
|
|
44
67
|
// Theme: 'light' | 'dark' | 'auto' (default: 'auto' follows system)
|
|
45
68
|
theme: 'auto',
|
|
46
69
|
|
|
47
|
-
// Accent color
|
|
70
|
+
// Accent color — must be a valid CSS color (hex, named, rgb/rgba, hsl/hsla)
|
|
48
71
|
accentColor: '#0071e3',
|
|
49
72
|
|
|
50
|
-
// Link to privacy policy
|
|
73
|
+
// Link to privacy policy — only http:/https:/mailto:/tel:/relative allowed
|
|
51
74
|
policyUrl: '/privacy',
|
|
52
75
|
|
|
53
76
|
// Show floating widget after consent
|
|
@@ -56,7 +79,7 @@ window.ZestConfig = {
|
|
|
56
79
|
// Consent expiration in days
|
|
57
80
|
expiration: 365,
|
|
58
81
|
|
|
59
|
-
// Callbacks
|
|
82
|
+
// Callbacks — wrapped in try/catch internally, safe to throw
|
|
60
83
|
callbacks: {
|
|
61
84
|
onAccept: (consent) => {},
|
|
62
85
|
onReject: () => {},
|
|
@@ -81,26 +104,85 @@ window.ZestConfig = {
|
|
|
81
104
|
## API
|
|
82
105
|
|
|
83
106
|
```javascript
|
|
84
|
-
// Show/hide UI
|
|
85
|
-
Zest.show()
|
|
86
|
-
Zest.hide()
|
|
87
|
-
Zest.showSettings()
|
|
88
|
-
Zest.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Zest.
|
|
93
|
-
Zest.
|
|
94
|
-
Zest.
|
|
107
|
+
// Show/hide UI (full build only)
|
|
108
|
+
Zest.show() // Show banner
|
|
109
|
+
Zest.hide() // Hide banner
|
|
110
|
+
Zest.showSettings() // Show settings modal
|
|
111
|
+
Zest.hideSettings() // Close settings modal
|
|
112
|
+
Zest.reset() // Clear consent + reshow banner
|
|
113
|
+
|
|
114
|
+
// Consent state
|
|
115
|
+
Zest.getConsent() // { essential, functional, analytics, marketing }
|
|
116
|
+
Zest.hasConsent('analytics') // boolean
|
|
117
|
+
Zest.hasConsentDecision() // boolean — has the user made a choice yet?
|
|
118
|
+
Zest.getConsentProof() // full consent cookie payload (compliance audit)
|
|
119
|
+
|
|
120
|
+
// Programmatic actions
|
|
121
|
+
Zest.acceptAll()
|
|
122
|
+
Zest.rejectAll()
|
|
123
|
+
Zest.updateConsent({ analytics: true, marketing: false }) // headless only
|
|
124
|
+
|
|
125
|
+
// DNT / GPC
|
|
126
|
+
Zest.isDoNotTrackEnabled()
|
|
127
|
+
Zest.getDNTDetails() // { enabled, source: 'dnt'|'gpc'|null }
|
|
128
|
+
|
|
129
|
+
// Events — subscribe helpers (also work with addEventListener)
|
|
130
|
+
Zest.on('zest:change', (e) => {})
|
|
131
|
+
Zest.once('zest:ready', (e) => {})
|
|
132
|
+
Zest.EVENTS // { READY, CONSENT, REJECT, CHANGE, SHOW, HIDE }
|
|
95
133
|
```
|
|
96
134
|
|
|
135
|
+
## Headless mode — bring your own UI
|
|
136
|
+
|
|
137
|
+
Full control over markup and styling, no Shadow DOM, no inline CSS.
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
import Zest from '@freshjuice/zest/headless';
|
|
141
|
+
|
|
142
|
+
Zest.init({
|
|
143
|
+
mode: 'safe',
|
|
144
|
+
respectDNT: true,
|
|
145
|
+
consentModeGoogle: true
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Decide when to show YOUR banner
|
|
149
|
+
if (!Zest.hasConsentDecision()) {
|
|
150
|
+
document.querySelector('#my-banner').classList.add('open');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Wire your buttons
|
|
154
|
+
document.querySelector('#accept').onclick = () => Zest.acceptAll();
|
|
155
|
+
document.querySelector('#reject').onclick = () => Zest.rejectAll();
|
|
156
|
+
|
|
157
|
+
document.querySelector('#save').onclick = () => {
|
|
158
|
+
Zest.updateConsent({
|
|
159
|
+
analytics: analyticsCheckbox.checked,
|
|
160
|
+
marketing: marketingCheckbox.checked,
|
|
161
|
+
functional: functionalCheckbox.checked
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Listen for changes
|
|
166
|
+
Zest.on(Zest.EVENTS.CHANGE, (e) => {
|
|
167
|
+
console.log('consent changed', e.detail.consent);
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
What headless gives you:
|
|
172
|
+
- All interceptors (cookies, storage, scripts) still work — just skip the built-in UI
|
|
173
|
+
- Same config surface (`mode`, `respectDNT`, `consentModeGoogle`, `blockedDomains`, `patterns`, etc.)
|
|
174
|
+
- **Does NOT auto-init** — you call `Zest.init()` when ready
|
|
175
|
+
- **Does NOT set `window.Zest`** — you import and use the module directly
|
|
176
|
+
|
|
177
|
+
See `examples/headless.html` for a complete working example.
|
|
178
|
+
|
|
97
179
|
## Do Not Track (DNT) / Global Privacy Control (GPC)
|
|
98
180
|
|
|
99
181
|
Zest respects browser privacy signals by default:
|
|
100
182
|
|
|
101
183
|
```javascript
|
|
102
184
|
window.ZestConfig = {
|
|
103
|
-
respectDNT: true,
|
|
185
|
+
respectDNT: true, // Respect DNT/GPC signals (default: true)
|
|
104
186
|
dntBehavior: 'reject' // What to do when DNT is enabled
|
|
105
187
|
};
|
|
106
188
|
```
|
|
@@ -111,10 +193,9 @@ window.ZestConfig = {
|
|
|
111
193
|
| `preselect` | Show banner with non-essential options unchecked |
|
|
112
194
|
| `ignore` | Ignore DNT/GPC signals completely |
|
|
113
195
|
|
|
114
|
-
**API methods:**
|
|
115
196
|
```javascript
|
|
116
|
-
Zest.isDoNotTrackEnabled() //
|
|
117
|
-
Zest.getDNTDetails() //
|
|
197
|
+
Zest.isDoNotTrackEnabled() // true if DNT or GPC is enabled
|
|
198
|
+
Zest.getDNTDetails() // { enabled: boolean, source: 'dnt' | 'gpc' | null }
|
|
118
199
|
```
|
|
119
200
|
|
|
120
201
|
## Blocking Modes
|
|
@@ -136,8 +217,6 @@ window.ZestConfig = {
|
|
|
136
217
|
|
|
137
218
|
### Custom Blocked Domains
|
|
138
219
|
|
|
139
|
-
Add your own domains to block:
|
|
140
|
-
|
|
141
220
|
```javascript
|
|
142
221
|
window.ZestConfig = {
|
|
143
222
|
mode: 'safe',
|
|
@@ -150,8 +229,6 @@ window.ZestConfig = {
|
|
|
150
229
|
|
|
151
230
|
### Manual Script Tagging
|
|
152
231
|
|
|
153
|
-
For any mode, you can explicitly tag scripts:
|
|
154
|
-
|
|
155
232
|
```html
|
|
156
233
|
<script data-consent-category="analytics" src="https://..."></script>
|
|
157
234
|
<script data-consent-category="marketing">
|
|
@@ -159,9 +236,11 @@ For any mode, you can explicitly tag scripts:
|
|
|
159
236
|
</script>
|
|
160
237
|
```
|
|
161
238
|
|
|
162
|
-
|
|
239
|
+
> **Note:** `data-consent-category="essential"` on third-party scripts is
|
|
240
|
+
> ignored — self-labeling as essential is a known bypass. Only
|
|
241
|
+
> `functional`, `analytics`, and `marketing` self-labels are honored.
|
|
163
242
|
|
|
164
|
-
|
|
243
|
+
### Allow Specific Scripts
|
|
165
244
|
|
|
166
245
|
```html
|
|
167
246
|
<script data-zest-allow src="https://cdn.example.com/library.js"></script>
|
|
@@ -185,11 +264,52 @@ document.addEventListener('zest:change', (e) => {
|
|
|
185
264
|
document.addEventListener('zest:ready', (e) => {
|
|
186
265
|
console.log('Zest initialized:', e.detail.consent);
|
|
187
266
|
});
|
|
267
|
+
|
|
268
|
+
// Or via the helpers
|
|
269
|
+
Zest.on(Zest.EVENTS.CHANGE, (e) => { /* ... */ });
|
|
270
|
+
Zest.once(Zest.EVENTS.READY, (e) => { /* ... */ });
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Google Consent Mode v2 / Microsoft UET Consent Mode
|
|
274
|
+
|
|
275
|
+
Optional — push consent state to Google and Microsoft advertising APIs.
|
|
276
|
+
|
|
277
|
+
### Enable via JavaScript
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
window.ZestConfig = {
|
|
281
|
+
consentModeGoogle: true,
|
|
282
|
+
consentModeMicrosoft: true
|
|
283
|
+
};
|
|
188
284
|
```
|
|
189
285
|
|
|
286
|
+
### Enable via data attributes
|
|
287
|
+
|
|
288
|
+
```html
|
|
289
|
+
<script
|
|
290
|
+
src="zest.min.js"
|
|
291
|
+
data-consent-mode-google="true"
|
|
292
|
+
data-consent-mode-microsoft="true"
|
|
293
|
+
></script>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
When enabled, Zest automatically:
|
|
297
|
+
|
|
298
|
+
1. Pushes a `'default'` denied state on page load (before any tracking scripts fire)
|
|
299
|
+
2. Pushes an `'update'` whenever the user makes a choice
|
|
300
|
+
|
|
301
|
+
### Category mapping
|
|
302
|
+
|
|
303
|
+
| Zest Category | Google Consent Mode v2 Signals | Microsoft UET Signal |
|
|
304
|
+
|---|---|---|
|
|
305
|
+
| `essential` | `functionality_storage: 'granted'` (always) | — |
|
|
306
|
+
| `functional` | `personalization_storage` | — |
|
|
307
|
+
| `analytics` | `analytics_storage` | — |
|
|
308
|
+
| `marketing` | `ad_storage`, `ad_user_data`, `ad_personalization` | `ad_storage` |
|
|
309
|
+
|
|
190
310
|
## Localization
|
|
191
311
|
|
|
192
|
-
|
|
312
|
+
Built-in translations with auto-detection.
|
|
193
313
|
|
|
194
314
|
**Supported languages:** `en`, `de`, `es`, `fr`, `it`, `pt`, `nl`, `pl`, `uk`, `ru`, `ja`, `zh`
|
|
195
315
|
|
|
@@ -197,8 +317,9 @@ Zest has **built-in translations** with auto-detection.
|
|
|
197
317
|
|
|
198
318
|
| Bundle | Size (gzip) | Description |
|
|
199
319
|
|--------|-------------|-------------|
|
|
200
|
-
| `zest.min.js` | ~
|
|
201
|
-
| `zest.{lang}.min.js` | ~
|
|
320
|
+
| `zest.min.js` | ~16 KB | All 12 languages, auto-detects |
|
|
321
|
+
| `zest.{lang}.min.js` | ~9 KB | Single language (e.g. `zest.de.min.js`) |
|
|
322
|
+
| `zest.headless.esm.min.js` | ~11 KB | Logic only, no UI / no translations (ESM import) |
|
|
202
323
|
|
|
203
324
|
```html
|
|
204
325
|
<!-- Full bundle - auto-detects language -->
|
|
@@ -211,61 +332,40 @@ Zest has **built-in translations** with auto-detection.
|
|
|
211
332
|
### Language Detection
|
|
212
333
|
|
|
213
334
|
```javascript
|
|
214
|
-
window.ZestConfig = {
|
|
215
|
-
lang: 'auto' // Auto-detect (default)
|
|
216
|
-
};
|
|
335
|
+
window.ZestConfig = { lang: 'auto' }; // default
|
|
217
336
|
```
|
|
218
337
|
|
|
219
|
-
|
|
220
|
-
1. `lang` config option (if not 'auto')
|
|
221
|
-
2. `<html lang="de">` attribute
|
|
222
|
-
3. Browser language (`navigator.language`)
|
|
223
|
-
4. Fallback to English
|
|
338
|
+
Priority: `lang` config → `<html lang="...">` → `navigator.language` → English.
|
|
224
339
|
|
|
225
340
|
### Force Specific Language
|
|
226
341
|
|
|
227
342
|
```javascript
|
|
228
|
-
window.ZestConfig = {
|
|
229
|
-
lang: 'de' // Force German
|
|
230
|
-
};
|
|
343
|
+
window.ZestConfig = { lang: 'de' };
|
|
231
344
|
```
|
|
232
345
|
|
|
233
346
|
### Override Labels
|
|
234
347
|
|
|
235
348
|
```javascript
|
|
236
349
|
window.ZestConfig = {
|
|
237
|
-
lang: 'de',
|
|
350
|
+
lang: 'de',
|
|
238
351
|
labels: {
|
|
239
352
|
banner: {
|
|
240
|
-
title: 'Custom German Title'
|
|
353
|
+
title: 'Custom German Title'
|
|
241
354
|
}
|
|
242
355
|
}
|
|
243
356
|
};
|
|
244
357
|
```
|
|
245
358
|
|
|
246
|
-
Standalone JSON translation files
|
|
359
|
+
Standalone JSON translation files are in `/locales/`.
|
|
247
360
|
|
|
248
|
-
##
|
|
361
|
+
## Styling the UI (full build)
|
|
249
362
|
|
|
250
|
-
|
|
363
|
+
The UI is rendered inside a Shadow DOM with `mode: 'open'`, so your global
|
|
364
|
+
CSS can't reach inside the component. You have three options:
|
|
251
365
|
|
|
252
|
-
|
|
253
|
-
window.ZestConfig = {
|
|
254
|
-
customStyles: `
|
|
255
|
-
.zest-banner {
|
|
256
|
-
max-width: 600px;
|
|
257
|
-
}
|
|
258
|
-
.zest-btn--primary {
|
|
259
|
-
border-radius: 20px;
|
|
260
|
-
}
|
|
261
|
-
.zest-modal {
|
|
262
|
-
max-width: 600px;
|
|
263
|
-
}
|
|
264
|
-
`
|
|
265
|
-
};
|
|
266
|
-
```
|
|
366
|
+
### 1. CSS custom properties (inheritable through Shadow DOM)
|
|
267
367
|
|
|
268
|
-
|
|
368
|
+
The following custom properties are exposed on the host elements:
|
|
269
369
|
|
|
270
370
|
```css
|
|
271
371
|
zest-banner, zest-modal, zest-widget {
|
|
@@ -280,6 +380,32 @@ zest-banner, zest-modal, zest-widget {
|
|
|
280
380
|
}
|
|
281
381
|
```
|
|
282
382
|
|
|
383
|
+
### 2. `customStyles` config option
|
|
384
|
+
|
|
385
|
+
```javascript
|
|
386
|
+
window.ZestConfig = {
|
|
387
|
+
customStyles: `
|
|
388
|
+
.zest-banner { max-width: 600px; }
|
|
389
|
+
.zest-btn--primary { border-radius: 20px; }
|
|
390
|
+
.zest-modal { max-width: 600px; }
|
|
391
|
+
`
|
|
392
|
+
};
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
> **Security note:** `customStyles` is sanitized — `@import`, `expression()`,
|
|
396
|
+
> external `url()` values, and selectors targeting the accept/reject
|
|
397
|
+
> buttons are stripped. This prevents clickjacking via invisible-button
|
|
398
|
+
> CSS attacks. Payloads over 20 KB are dropped entirely.
|
|
399
|
+
|
|
400
|
+
### 3. Style the host elements directly
|
|
401
|
+
|
|
402
|
+
The custom elements `zest-banner`, `zest-modal`, `zest-widget` live in the
|
|
403
|
+
light DOM — you can position, hide, or z-index them from your global CSS.
|
|
404
|
+
|
|
405
|
+
### Want full CSS control?
|
|
406
|
+
|
|
407
|
+
Use the **headless** entry and style your own markup however you like.
|
|
408
|
+
|
|
283
409
|
## Categories
|
|
284
410
|
|
|
285
411
|
| Category | ID | Default | Description |
|
|
@@ -289,18 +415,38 @@ zest-banner, zest-modal, zest-widget {
|
|
|
289
415
|
| Analytics | `analytics` | OFF | Usage tracking |
|
|
290
416
|
| Marketing | `marketing` | OFF | Advertising cookies |
|
|
291
417
|
|
|
418
|
+
Unknown cookies default to `marketing` (strictest).
|
|
419
|
+
|
|
420
|
+
## Security
|
|
421
|
+
|
|
422
|
+
Zest takes a defense-in-depth approach to security.
|
|
423
|
+
|
|
424
|
+
Highlights:
|
|
425
|
+
|
|
426
|
+
- All config-driven HTML is escaped via an internal `escapeHTML` pass
|
|
427
|
+
- `policyUrl` is validated against an allowlist (`http:`, `https:`,
|
|
428
|
+
`mailto:`, `tel:`, relative)
|
|
429
|
+
- `accentColor` must pass a strict color validator
|
|
430
|
+
- `customStyles` is sanitized (see above)
|
|
431
|
+
- Consent cookie JSON is schema-validated on read (prototype pollution safe)
|
|
432
|
+
- On HTTPS, the consent cookie is written with the `Secure` flag
|
|
433
|
+
- `window.Zest` is frozen and non-configurable once installed
|
|
434
|
+
- User callbacks are wrapped in try/catch so a throwing handler can't
|
|
435
|
+
break the consent flow
|
|
436
|
+
- Cookie / storage / script queues are size-capped (DoS prevention)
|
|
437
|
+
|
|
438
|
+
To report a vulnerability, open a private security advisory on GitHub.
|
|
439
|
+
|
|
292
440
|
## Config Schema
|
|
293
441
|
|
|
294
|
-
JSON Schema
|
|
442
|
+
JSON Schema for IDE autocompletion: [`zest.config.schema.json`](zest.config.schema.json)
|
|
295
443
|
|
|
296
444
|
## Contributing
|
|
297
445
|
|
|
298
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
299
|
-
|
|
300
446
|
1. Fork the repository
|
|
301
447
|
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
302
|
-
3. Commit your changes
|
|
303
|
-
4. Push to the branch
|
|
448
|
+
3. Commit your changes
|
|
449
|
+
4. Push to the branch
|
|
304
450
|
5. Open a Pull Request
|
|
305
451
|
|
|
306
452
|
## Credits
|