@freshjuice/zest 2.0.0 → 2.2.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/dist/zest.d.ts +247 -0
- package/dist/zest.de.js +107 -3
- package/dist/zest.de.js.map +1 -1
- package/dist/zest.de.min.js +1 -1
- package/dist/zest.en.js +107 -3
- package/dist/zest.en.js.map +1 -1
- package/dist/zest.en.min.js +1 -1
- package/dist/zest.es.js +107 -3
- package/dist/zest.es.js.map +1 -1
- package/dist/zest.es.min.js +1 -1
- package/dist/zest.esm.js +107 -3
- package/dist/zest.esm.js.map +1 -1
- package/dist/zest.esm.min.js +1 -1
- package/dist/zest.fr.js +107 -3
- package/dist/zest.fr.js.map +1 -1
- package/dist/zest.fr.min.js +1 -1
- package/dist/zest.headless.d.ts +211 -0
- package/dist/zest.headless.esm.js +107 -3
- package/dist/zest.headless.esm.js.map +1 -1
- package/dist/zest.headless.esm.min.js +1 -1
- package/dist/zest.it.js +107 -3
- package/dist/zest.it.js.map +1 -1
- package/dist/zest.it.min.js +1 -1
- package/dist/zest.ja.js +107 -3
- package/dist/zest.ja.js.map +1 -1
- package/dist/zest.ja.min.js +1 -1
- package/dist/zest.js +107 -3
- package/dist/zest.js.map +1 -1
- package/dist/zest.min.js +1 -1
- package/dist/zest.nl.js +107 -3
- package/dist/zest.nl.js.map +1 -1
- package/dist/zest.nl.min.js +1 -1
- package/dist/zest.pl.js +107 -3
- package/dist/zest.pl.js.map +1 -1
- package/dist/zest.pl.min.js +1 -1
- package/dist/zest.pt.js +107 -3
- package/dist/zest.pt.js.map +1 -1
- package/dist/zest.pt.min.js +1 -1
- package/dist/zest.ru.js +107 -3
- package/dist/zest.ru.js.map +1 -1
- package/dist/zest.ru.min.js +1 -1
- package/dist/zest.uk.js +107 -3
- package/dist/zest.uk.js.map +1 -1
- package/dist/zest.uk.min.js +1 -1
- package/dist/zest.zh.js +107 -3
- package/dist/zest.zh.js.map +1 -1
- package/dist/zest.zh.min.js +1 -1
- package/package.json +9 -2
- package/src/config/defaults.js +48 -0
- package/src/core/pattern-matcher.js +37 -0
- package/src/core-lifecycle.js +23 -4
- package/src/types/zest.d.ts +247 -0
- package/src/types/zest.headless.d.ts +211 -0
package/dist/zest.d.ts
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for `@freshjuice/zest` (full build with UI).
|
|
3
|
+
*
|
|
4
|
+
* The full build ships the consent engine plus a Shadow-DOM banner,
|
|
5
|
+
* settings modal, and floating widget. It auto-initialises on script
|
|
6
|
+
* load when included via `<script>`, or you can drive it manually via
|
|
7
|
+
* `Zest.init()`.
|
|
8
|
+
*
|
|
9
|
+
* For a logic-only build without UI, import from
|
|
10
|
+
* `@freshjuice/zest/headless` instead.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import Zest from '@freshjuice/zest';
|
|
15
|
+
*
|
|
16
|
+
* Zest.init({
|
|
17
|
+
* position: 'bottom-right',
|
|
18
|
+
* theme: 'auto',
|
|
19
|
+
* accentColor: '#0071e3',
|
|
20
|
+
* policyUrl: '/privacy'
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** Built-in consent categories. */
|
|
26
|
+
export type ConsentCategory =
|
|
27
|
+
| 'essential'
|
|
28
|
+
| 'functional'
|
|
29
|
+
| 'analytics'
|
|
30
|
+
| 'marketing';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Per-category boolean consent state. `essential` is always `true` —
|
|
34
|
+
* consent for it cannot be revoked because it covers strictly-necessary
|
|
35
|
+
* processing.
|
|
36
|
+
*/
|
|
37
|
+
export type ConsentState =
|
|
38
|
+
& Partial<Record<ConsentCategory, boolean>>
|
|
39
|
+
& { essential: true };
|
|
40
|
+
|
|
41
|
+
/** Snapshot returned by `init()`. */
|
|
42
|
+
export interface InitSnapshot {
|
|
43
|
+
consent: ConsentState;
|
|
44
|
+
hasDecision: boolean;
|
|
45
|
+
dntApplied: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Tamper-evident proof of the user's last consent decision. */
|
|
49
|
+
export interface ConsentProof {
|
|
50
|
+
version: string;
|
|
51
|
+
timestamp: number;
|
|
52
|
+
categories: ConsentState;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Output of `getDNTDetails()`. */
|
|
56
|
+
export interface DNTDetails {
|
|
57
|
+
dnt: boolean;
|
|
58
|
+
gpc: boolean;
|
|
59
|
+
doNotTrack: string | null;
|
|
60
|
+
globalPrivacyControl: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Behaviour when DNT / GPC is detected at init time. */
|
|
64
|
+
export type DNTBehavior = 'reject' | 'preselect' | 'ignore';
|
|
65
|
+
|
|
66
|
+
/** Banner position on the page. */
|
|
67
|
+
export type BannerPosition = 'bottom' | 'bottom-left' | 'bottom-right' | 'top';
|
|
68
|
+
|
|
69
|
+
/** UI theme. `auto` follows `prefers-color-scheme`. */
|
|
70
|
+
export type ZestTheme = 'light' | 'dark' | 'auto';
|
|
71
|
+
|
|
72
|
+
/** Script-blocking strictness. */
|
|
73
|
+
export type ZestMode = 'manual' | 'safe' | 'strict' | 'doomsday';
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Optional consumer callbacks. Each is wrapped in a try/catch internally
|
|
77
|
+
* so a thrown error never breaks the consent pipeline.
|
|
78
|
+
*/
|
|
79
|
+
export interface ZestCallbacks {
|
|
80
|
+
onAccept?: (consent: ConsentState) => void;
|
|
81
|
+
onReject?: (consent: ConsentState) => void;
|
|
82
|
+
onChange?: (consent: ConsentState) => void;
|
|
83
|
+
onReady?: (consent: ConsentState) => void;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Granular toggles for Zest's interceptor layer. Default is `true` on
|
|
88
|
+
* every channel — back-compat with previous versions.
|
|
89
|
+
*
|
|
90
|
+
* Consumers that gate optional scripts and storage themselves can
|
|
91
|
+
* disable interception per channel and use Zest as a pure consent-state
|
|
92
|
+
* engine.
|
|
93
|
+
*/
|
|
94
|
+
export interface InterceptToggles {
|
|
95
|
+
cookies?: boolean;
|
|
96
|
+
storage?: boolean;
|
|
97
|
+
scripts?: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Configuration accepted by `init()` and `window.ZestConfig`. */
|
|
101
|
+
export interface InitOptions {
|
|
102
|
+
/** Display language. `'auto'` detects from `<html lang>` / browser. */
|
|
103
|
+
lang?:
|
|
104
|
+
| 'auto'
|
|
105
|
+
| 'en' | 'de' | 'es' | 'fr' | 'it' | 'pt'
|
|
106
|
+
| 'nl' | 'pl' | 'uk' | 'ru' | 'ja' | 'zh';
|
|
107
|
+
/** Banner position. Default `'bottom'`. */
|
|
108
|
+
position?: BannerPosition;
|
|
109
|
+
/** UI theme. Default `'auto'`. */
|
|
110
|
+
theme?: ZestTheme;
|
|
111
|
+
/** Hex accent color for buttons (e.g. `'#0071e3'`). */
|
|
112
|
+
accentColor?: string;
|
|
113
|
+
/** Link to the host site's privacy policy. */
|
|
114
|
+
policyUrl?: string;
|
|
115
|
+
/** Show floating "manage cookies" widget after a decision. Default `true`. */
|
|
116
|
+
showWidget?: boolean;
|
|
117
|
+
/** Cookie expiration in days. Default `365`. */
|
|
118
|
+
expiration?: number;
|
|
119
|
+
/** Script-blocking mode. Default `'safe'`. */
|
|
120
|
+
mode?: ZestMode;
|
|
121
|
+
/** Auto-initialise on script load. Default `true` for the UI build. */
|
|
122
|
+
autoInit?: boolean;
|
|
123
|
+
/** Respect Do Not Track / Global Privacy Control. Default `true`. */
|
|
124
|
+
respectDNT?: boolean;
|
|
125
|
+
/** What to do when DNT/GPC is on. Default `'reject'`. */
|
|
126
|
+
dntBehavior?: DNTBehavior;
|
|
127
|
+
/** Disable individual interceptors. Default: all on. */
|
|
128
|
+
intercept?: InterceptToggles;
|
|
129
|
+
/**
|
|
130
|
+
* Exact storage / cookie names to treat as strictly-necessary. Each
|
|
131
|
+
* is appended to the essential category as a fully-anchored regex,
|
|
132
|
+
* so the built-in essential patterns (zest_*, csrf*, …) stay intact.
|
|
133
|
+
*/
|
|
134
|
+
essentialKeys?: string[];
|
|
135
|
+
/**
|
|
136
|
+
* Regex source strings to treat as strictly-necessary. Validated via
|
|
137
|
+
* safeRegExp, appended (not replaced) to the essential category.
|
|
138
|
+
*/
|
|
139
|
+
essentialPatterns?: string[];
|
|
140
|
+
/**
|
|
141
|
+
* Override patterns per category. Note: this REPLACES the category's
|
|
142
|
+
* built-in patterns. Prefer `essentialKeys` / `essentialPatterns` if
|
|
143
|
+
* you only want to add to the essential category.
|
|
144
|
+
*/
|
|
145
|
+
patterns?: Partial<Record<ConsentCategory, string[]>>;
|
|
146
|
+
/** Consumer callbacks. */
|
|
147
|
+
callbacks?: ZestCallbacks;
|
|
148
|
+
/** Anything else — Zest tolerates unknown keys at runtime. */
|
|
149
|
+
[key: string]: unknown;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Event names emitted on `document.documentElement`. */
|
|
153
|
+
export interface ZestEvents {
|
|
154
|
+
READY: 'zest:ready';
|
|
155
|
+
CONSENT: 'zest:consent';
|
|
156
|
+
REJECT: 'zest:reject';
|
|
157
|
+
CHANGE: 'zest:change';
|
|
158
|
+
SHOW: 'zest:show';
|
|
159
|
+
HIDE: 'zest:hide';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export type ZestEventName = ZestEvents[keyof ZestEvents];
|
|
163
|
+
|
|
164
|
+
/** Detail payload of consent events. */
|
|
165
|
+
export interface ZestEventDetail {
|
|
166
|
+
consent: ConsentState;
|
|
167
|
+
previous?: ConsentState;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
declare const Zest: {
|
|
171
|
+
/** Initialise. Auto-called when the script loads unless `autoInit: false`. */
|
|
172
|
+
init(options?: InitOptions): InitSnapshot;
|
|
173
|
+
|
|
174
|
+
/** Show the consent banner. */
|
|
175
|
+
show(): void;
|
|
176
|
+
|
|
177
|
+
/** Hide the consent banner. */
|
|
178
|
+
hide(): void;
|
|
179
|
+
|
|
180
|
+
/** Open the per-category settings modal. */
|
|
181
|
+
showSettings(): void;
|
|
182
|
+
|
|
183
|
+
/** Close the settings modal. */
|
|
184
|
+
hideSettings(): void;
|
|
185
|
+
|
|
186
|
+
/** Show the persistent "manage cookies" widget. */
|
|
187
|
+
showWidget(): void;
|
|
188
|
+
|
|
189
|
+
/** Hide the widget without removing it. */
|
|
190
|
+
hideWidget(): void;
|
|
191
|
+
|
|
192
|
+
/** Current consent state (clone, safe to mutate). */
|
|
193
|
+
getConsent(): ConsentState;
|
|
194
|
+
|
|
195
|
+
/** Has the user granted consent for `category`? */
|
|
196
|
+
hasConsent(category: ConsentCategory): boolean;
|
|
197
|
+
|
|
198
|
+
/** Has the user made any consent decision yet? */
|
|
199
|
+
hasConsentDecision(): boolean;
|
|
200
|
+
|
|
201
|
+
/** Tamper-evident snapshot of the last consent decision. */
|
|
202
|
+
getConsentProof(): ConsentProof | null;
|
|
203
|
+
|
|
204
|
+
/** Grant consent for every category and run accept callbacks. */
|
|
205
|
+
acceptAll(): void;
|
|
206
|
+
|
|
207
|
+
/** Revoke consent for every non-essential category and run reject callbacks. */
|
|
208
|
+
rejectAll(): void;
|
|
209
|
+
|
|
210
|
+
/** Wipe all consent state and reshow the banner. */
|
|
211
|
+
reset(): void;
|
|
212
|
+
|
|
213
|
+
/** True if the browser is sending DNT or GPC. */
|
|
214
|
+
isDoNotTrackEnabled(): boolean;
|
|
215
|
+
|
|
216
|
+
/** Why `isDoNotTrackEnabled()` returned what it did. */
|
|
217
|
+
getDNTDetails(): DNTDetails;
|
|
218
|
+
|
|
219
|
+
/** Subscribe to a consent event. Returns an unsubscribe function. */
|
|
220
|
+
on(
|
|
221
|
+
eventName: ZestEventName,
|
|
222
|
+
handler: (event: CustomEvent<ZestEventDetail>) => void
|
|
223
|
+
): () => void;
|
|
224
|
+
|
|
225
|
+
/** Subscribe once; auto-unsubscribes after the first call. */
|
|
226
|
+
once(
|
|
227
|
+
eventName: ZestEventName,
|
|
228
|
+
handler: (event: CustomEvent<ZestEventDetail>) => void
|
|
229
|
+
): () => void;
|
|
230
|
+
|
|
231
|
+
/** Constants for `on()` / `once()`. */
|
|
232
|
+
EVENTS: ZestEvents;
|
|
233
|
+
|
|
234
|
+
/** Active configuration after `init()`. */
|
|
235
|
+
getConfig(): InitOptions | null;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export default Zest;
|
|
239
|
+
|
|
240
|
+
declare global {
|
|
241
|
+
interface Window {
|
|
242
|
+
/** Set before loading `zest.min.js` to configure auto-initialisation. */
|
|
243
|
+
ZestConfig?: InitOptions;
|
|
244
|
+
/** The Zest singleton, attached after auto-init. */
|
|
245
|
+
Zest?: typeof Zest;
|
|
246
|
+
}
|
|
247
|
+
}
|
package/dist/zest.de.js
CHANGED
|
@@ -258,10 +258,47 @@ var Zest = (function () {
|
|
|
258
258
|
|
|
259
259
|
let patterns = { ...DEFAULT_PATTERNS };
|
|
260
260
|
|
|
261
|
+
/** Escape a string so it can be embedded in a regex literal verbatim. */
|
|
262
|
+
function escapeRegex(value) {
|
|
263
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Append patterns to a single category without replacing what's already
|
|
268
|
+
* there. Used by `essentialKeys` and `essentialPatterns` config to extend
|
|
269
|
+
* the strictly-necessary category with consumer-specific entries while
|
|
270
|
+
* keeping the built-in defaults (zest_*, csrf*, xsrf*, etc.).
|
|
271
|
+
*
|
|
272
|
+
* `keys` is an array of exact storage/cookie names; each one is
|
|
273
|
+
* compiled as a fully-anchored regex via `escapeRegex`.
|
|
274
|
+
* `patternStrings` is an array of regex source strings, each validated
|
|
275
|
+
* via `safeRegExp`. Invalid entries are dropped silently.
|
|
276
|
+
*/
|
|
277
|
+
function appendPatternsToCategory(category, { keys = [], patternStrings = [] } = {}) {
|
|
278
|
+
if (!patterns[category]) patterns[category] = [];
|
|
279
|
+
|
|
280
|
+
for (const key of keys) {
|
|
281
|
+
if (typeof key !== 'string' || !key) continue;
|
|
282
|
+
const re = safeRegExp(`^${escapeRegex(key)}$`);
|
|
283
|
+
if (re) patterns[category].push(re);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for (const p of patternStrings) {
|
|
287
|
+
if (typeof p !== 'string' || !p) continue;
|
|
288
|
+
const re = safeRegExp(p);
|
|
289
|
+
if (re) patterns[category].push(re);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
261
293
|
/**
|
|
262
294
|
* Set custom patterns. User-supplied strings are validated with safeRegExp,
|
|
263
295
|
* which rejects catastrophic-backtracking shapes and syntax errors.
|
|
264
296
|
* Invalid patterns are silently dropped with a console warning.
|
|
297
|
+
*
|
|
298
|
+
* Note: this REPLACES the patterns for any category present in
|
|
299
|
+
* `customPatterns`. To extend the essential category without losing the
|
|
300
|
+
* built-in defaults, use `appendPatternsToCategory()` (or pass
|
|
301
|
+
* `essentialKeys` / `essentialPatterns` to `Zest.init()`).
|
|
265
302
|
*/
|
|
266
303
|
function setPatterns(customPatterns) {
|
|
267
304
|
patterns = { ...DEFAULT_PATTERNS };
|
|
@@ -1292,6 +1329,32 @@ var Zest = (function () {
|
|
|
1292
1329
|
// Blocking mode: 'manual' | 'safe' | 'strict' | 'doomsday'
|
|
1293
1330
|
mode: 'safe',
|
|
1294
1331
|
|
|
1332
|
+
// Interceptor toggles. By default Zest installs cookie + storage
|
|
1333
|
+
// interceptors that route writes through the consent layer. Consumers
|
|
1334
|
+
// who manage gating themselves (typically headless mode with custom
|
|
1335
|
+
// analytics integrations) can opt out per channel.
|
|
1336
|
+
intercept: {
|
|
1337
|
+
cookies: true,
|
|
1338
|
+
storage: true,
|
|
1339
|
+
scripts: true
|
|
1340
|
+
},
|
|
1341
|
+
|
|
1342
|
+
// Strictly-necessary declarations. Both fields *append* to whatever
|
|
1343
|
+
// the essential category already matches via the pattern matcher
|
|
1344
|
+
// defaults — they do not replace.
|
|
1345
|
+
//
|
|
1346
|
+
// - essentialKeys: array of exact storage / cookie names to treat
|
|
1347
|
+
// as strictly-necessary. Easiest case.
|
|
1348
|
+
// - essentialPatterns: array of regex source strings, validated via
|
|
1349
|
+
// safeRegExp. For prefix or family matches.
|
|
1350
|
+
//
|
|
1351
|
+
// Use these instead of `patterns.essential` when you only want to
|
|
1352
|
+
// ADD entries to the essential category without replacing the
|
|
1353
|
+
// built-in patterns (zest_*, csrf*, xsrf*, session*, __host-*,
|
|
1354
|
+
// __secure-*).
|
|
1355
|
+
essentialKeys: [],
|
|
1356
|
+
essentialPatterns: [],
|
|
1357
|
+
|
|
1295
1358
|
// Custom domains to block (in addition to mode-based blocking)
|
|
1296
1359
|
blockedDomains: [], // days
|
|
1297
1360
|
|
|
@@ -1374,6 +1437,28 @@ var Zest = (function () {
|
|
|
1374
1437
|
config.patterns = userConfig.patterns;
|
|
1375
1438
|
}
|
|
1376
1439
|
|
|
1440
|
+
// Interceptor toggles — shallow-merge so consumers can pass partial
|
|
1441
|
+
// overrides like `intercept: { storage: false }` without losing the
|
|
1442
|
+
// other defaults.
|
|
1443
|
+
if (userConfig.intercept && typeof userConfig.intercept === 'object') {
|
|
1444
|
+
config.intercept = {
|
|
1445
|
+
...DEFAULTS.intercept,
|
|
1446
|
+
...userConfig.intercept
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// Strictly-necessary declarations
|
|
1451
|
+
if (Array.isArray(userConfig.essentialKeys)) {
|
|
1452
|
+
config.essentialKeys = userConfig.essentialKeys.filter(
|
|
1453
|
+
(k) => typeof k === 'string' && k.length > 0 && k.length <= 200
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
if (Array.isArray(userConfig.essentialPatterns)) {
|
|
1457
|
+
config.essentialPatterns = userConfig.essentialPatterns.filter(
|
|
1458
|
+
(p) => typeof p === 'string' && p.length > 0 && p.length <= 500
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1377
1462
|
return config;
|
|
1378
1463
|
}
|
|
1379
1464
|
|
|
@@ -1816,13 +1901,32 @@ var Zest = (function () {
|
|
|
1816
1901
|
setPatterns(currentConfig.patterns);
|
|
1817
1902
|
}
|
|
1818
1903
|
|
|
1904
|
+
// Append consumer-declared strictly-necessary entries on top of
|
|
1905
|
+
// whatever's already in the essential category. This is the friendly
|
|
1906
|
+
// alternative to overriding via `patterns.essential` directly.
|
|
1907
|
+
if (
|
|
1908
|
+
(Array.isArray(currentConfig.essentialKeys) && currentConfig.essentialKeys.length > 0) ||
|
|
1909
|
+
(Array.isArray(currentConfig.essentialPatterns) && currentConfig.essentialPatterns.length > 0)
|
|
1910
|
+
) {
|
|
1911
|
+
appendPatternsToCategory('essential', {
|
|
1912
|
+
keys: currentConfig.essentialKeys,
|
|
1913
|
+
patternStrings: currentConfig.essentialPatterns
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1819
1917
|
setConsentChecker$2(checkConsent);
|
|
1820
1918
|
setConsentChecker$1(checkConsent);
|
|
1821
1919
|
setConsentChecker(checkConsent);
|
|
1822
1920
|
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1921
|
+
// Interceptor toggles. By default everything is intercepted (back-compat
|
|
1922
|
+
// with v2.0 / v2.1). Consumers that gate scripts and storage themselves
|
|
1923
|
+
// can opt out per channel via `intercept: { storage: false, … }`.
|
|
1924
|
+
const intercept = currentConfig.intercept || { cookies: true, storage: true, scripts: true };
|
|
1925
|
+
if (intercept.cookies !== false) interceptCookies();
|
|
1926
|
+
if (intercept.storage !== false) interceptStorage();
|
|
1927
|
+
if (intercept.scripts !== false) {
|
|
1928
|
+
startScriptBlocking(currentConfig.mode, currentConfig.blockedDomains);
|
|
1929
|
+
}
|
|
1826
1930
|
|
|
1827
1931
|
const consent = loadConsent();
|
|
1828
1932
|
initialized = true;
|